mirror of
https://github.com/cline/cline.git
synced 2026-10-25 10:26:36 +08:00
* docs: add enforced .clineignore guard plugin example Adds clineignore-read-files-guard.ts, a beforeTool hook plugin that blocks read_files, editor, apply_patch, and run_commands calls targeting paths matching gitignore-style patterns in a workspace .clineignore file, and protects .clineignore itself from modification. Features it on the .clineignore docs page as the enforced replacement for the deprecated built-in feature. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * docs: use a PreToolUse file hook for the enforced .clineignore example Replaces the plugin-based guard with a PreToolUse hook script that works in the VS Code extension today (.clinerules/hooks/PreToolUse plus the Enable Hooks setting) as well as the CLI (.cline/hooks/PreToolUse.sh). The script handles both hook payload shapes, blocks read_files/editor/apply_patch/ run_commands calls matching .clineignore patterns, and protects .clineignore itself from modification. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix: canonicalize paths in .clineignore guard hook Lexically collapse '.', '..', and empty segments before the ignore match and the .clineignore self-protection check, closing bypasses via noncanonical paths like ./.clineignore, secrets/../.env, or /root/./file (Greptile review finding on #13649). Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * docs: note symlink limitation in .clineignore guard hook docs Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
362 lines
14 KiB
Plaintext
362 lines
14 KiB
Plaintext
---
|
|
title: ".clineignore (deprecate soon)"
|
|
sidebarTitle: ".clineignore (deprecate soon)"
|
|
description: "Control which files and directories Cline can access in your project. This feature will be deprecated soon."
|
|
---
|
|
|
|
<Warning>
|
|
**`.clineignore` will be deprecated soon.**
|
|
|
|
`.clineignore` filters what Cline loads automatically, but it is not a security or access-control boundary — ignored files can still be read via explicit `@` mentions or shell commands. We're moving away from it as a supported feature.
|
|
</Warning>
|
|
|
|
## Enforcing .clineignore with a Hook
|
|
|
|
While `.clineignore` is being phased out as a built-in feature, you can keep using the same file — and get a **stronger, enforced** restriction than the original ever provided — with a `PreToolUse` hook. The original `.clineignore` only filtered automatic context loading; the hook script below actively **blocks** the tool call whenever a file read (`read_files`), edit (`editor`, `apply_patch`), or shell command (`run_commands`) targets a file matching a `.clineignore` pattern.
|
|
|
|
### Install in the VS Code extension
|
|
|
|
Hooks in the extension live in `.clinerules/hooks/` (workspace) or `~/Documents/Cline/Hooks/` (global), must be named exactly after their event with no file extension, and need a shebang and the executable bit:
|
|
|
|
```bash
|
|
mkdir -p .clinerules/hooks
|
|
curl -o .clinerules/hooks/PreToolUse https://raw.githubusercontent.com/cline/cline/main/sdk/examples/hooks/PreToolUse_ClineignoreGuard.sh
|
|
chmod +x .clinerules/hooks/PreToolUse
|
|
```
|
|
|
|
Then check **Enable Hooks** in Cline's feature settings.
|
|
|
|
### Install in the CLI
|
|
|
|
The CLI discovers hooks from `.cline/hooks/` in the workspace (or `~/.cline/hooks/` globally, or a custom `--hooks-dir`):
|
|
|
|
```bash
|
|
mkdir -p .cline/hooks
|
|
curl -o .cline/hooks/PreToolUse.sh https://raw.githubusercontent.com/cline/cline/main/sdk/examples/hooks/PreToolUse_ClineignoreGuard.sh
|
|
chmod +x .cline/hooks/PreToolUse.sh
|
|
```
|
|
|
|
### List the files to protect
|
|
|
|
Add a `.clineignore` file at your workspace root. Patterns use `.gitignore` syntax — directories, globs, and `!` negations all work:
|
|
|
|
```text
|
|
# .clineignore
|
|
.env
|
|
.env.*
|
|
secrets/
|
|
*.pem
|
|
```
|
|
|
|
When Cline tries to touch a matching file, the hook cancels the tool call before it runs — the file is never accessed, and the current task run stops. In the extension you'll see the `PreToolUse` hook row followed by an **Aborted** status; in the CLI the run ends. Send a follow-up message to continue the conversation.
|
|
|
|
The hook records the block reason in its output:
|
|
|
|
```json
|
|
{
|
|
"cancel": true,
|
|
"errorMessage": "Blocked read_files: .env matched a .clineignore pattern, so Cline may not access it. Update .clineignore if access should be allowed."
|
|
}
|
|
```
|
|
|
|
### The hook script
|
|
|
|
The script handles both hook payload shapes (the extension's `preToolUse.parameters` and the CLI's `tool_call.input`), evaluates paths with real gitignore semantics via `git check-ignore` scoped to `.clineignore` alone, and also blocks attempts to modify `.clineignore` itself so the agent cannot un-ignore files. It requires `jq` and `git` — though `git` is used purely as a pattern matcher, so your workspace does not need to be a git repository.
|
|
|
|
<Accordion title="PreToolUse_ClineignoreGuard.sh (full script)">
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# Cline Hook: PreToolUse (.clineignore guard)
|
|
# Blocks file reads, edits, and shell commands whose paths match
|
|
# gitignore-style patterns listed in <workspace>/.clineignore.
|
|
#
|
|
# Works with both file-hook payload shapes:
|
|
# - VS Code extension hooks (.clinerules/hooks/PreToolUse):
|
|
# .preToolUse.toolName + .preToolUse.parameters (values JSON-stringified)
|
|
# - CLI / SDK file hooks (.cline/hooks/PreToolUse.sh):
|
|
# .tool_call.name + .tool_call.input
|
|
#
|
|
# Install (VS Code extension — name must be exactly "PreToolUse"):
|
|
# mkdir -p .clinerules/hooks
|
|
# cp PreToolUse_ClineignoreGuard.sh .clinerules/hooks/PreToolUse
|
|
# chmod +x .clinerules/hooks/PreToolUse
|
|
# ...and check "Enable Hooks" in Cline's feature settings.
|
|
#
|
|
# Install (CLI):
|
|
# mkdir -p .cline/hooks
|
|
# cp PreToolUse_ClineignoreGuard.sh .cline/hooks/PreToolUse.sh
|
|
# chmod +x .cline/hooks/PreToolUse.sh
|
|
#
|
|
# Then list the files to protect in a .clineignore file at your workspace
|
|
# root using .gitignore syntax (directories, globs, and ! negations work).
|
|
#
|
|
# Requires: jq, git (used only as a pattern matcher; the workspace does not
|
|
# need to be a git repository).
|
|
|
|
set -eu
|
|
|
|
input=$(cat)
|
|
|
|
tool=$(echo "$input" | jq -r '.tool_call.name // .preToolUse.toolName // ""')
|
|
|
|
# Only guard tools that read or write files or run shell commands.
|
|
case "$tool" in
|
|
read_files|editor|apply_patch|run_commands) ;;
|
|
*) echo '{"cancel": false}'; exit 0 ;;
|
|
esac
|
|
|
|
root=$(echo "$input" | jq -r '.workspaceRoots[0] // empty')
|
|
[ -n "$root" ] || root="$PWD"
|
|
ignore_file="$root/.clineignore"
|
|
if [ ! -f "$ignore_file" ]; then
|
|
echo '{"cancel": false}'
|
|
exit 0
|
|
fi
|
|
|
|
# Normalize the tool input: prefer the rich CLI shape, fall back to the
|
|
# extension's stringified parameters (dejson re-parses those values).
|
|
jq_prelude='
|
|
def dejson: if type == "string" then (fromjson? // .) else . end;
|
|
((.tool_call.input // .preToolUse.parameters // {}) | dejson) as $in |'
|
|
|
|
# Collect every candidate path from the tool input.
|
|
case "$tool" in
|
|
apply_patch)
|
|
# apply_patch carries its paths inside the patch body headers.
|
|
paths=$(echo "$input" | jq -r "$jq_prelude"'
|
|
if ($in | type) == "string" then $in
|
|
elif ($in | type) == "object" then ($in.input // "" | dejson)
|
|
else "" end | strings' \
|
|
| sed -n \
|
|
-e 's/^\*\*\* Add File: //p' \
|
|
-e 's/^\*\*\* Update File: //p' \
|
|
-e 's/^\*\*\* Delete File: //p' \
|
|
-e 's/^\*\*\* Move to: //p')
|
|
;;
|
|
run_commands)
|
|
# Conservative shell guard: treat every token of every command as a
|
|
# candidate path. Catches straightforward access like `cat .env`
|
|
# without attempting full shell parsing.
|
|
paths=$(echo "$input" | jq -r "$jq_prelude"'
|
|
(if ($in | type) == "string" then [$in]
|
|
elif ($in | type) == "array" then $in
|
|
elif ($in | type) == "object" then
|
|
[($in.commands // $in.command // $in.cmd // empty) | dejson] | flatten
|
|
else [] end)
|
|
| .[]
|
|
| if type == "object"
|
|
then ((.command // empty), ((.args // []) | dejson | .[]?))
|
|
else . end
|
|
| strings' \
|
|
| tr -s '[:space:];|&()<>' '\n' \
|
|
| sed -e "s/^[\"']*//" -e "s/[\"']*\$//" \
|
|
| grep -v '^-' | grep -v '^$' || true)
|
|
;;
|
|
*)
|
|
# read_files / editor accept a few input shapes:
|
|
# {files: [{path}]}, {path}, plain strings, string arrays, aliases...
|
|
paths=$(echo "$input" | jq -r "$jq_prelude"'
|
|
(if ($in | type) == "string" then [$in]
|
|
elif ($in | type) == "array" then $in
|
|
elif ($in | type) == "object" then
|
|
[$in.path?, $in.file_path?, $in.filePath?]
|
|
+ (($in.files // []) | dejson | if type == "array" then . else [.] end)
|
|
+ (($in.paths // []) | dejson | if type == "array" then . else [.] end)
|
|
+ (($in.file_paths // []) | dejson | if type == "array" then . else [.] end)
|
|
else [] end)
|
|
| .[]
|
|
| dejson
|
|
| if type == "object" then (.path // .file_path // .filePath // empty) else . end
|
|
| strings')
|
|
;;
|
|
esac
|
|
|
|
# Lexically collapse ".", "..", and empty segments of an absolute path, so
|
|
# noncanonical forms like ./.clineignore or secrets/../.env cannot slip
|
|
# past the checks below.
|
|
normalize_abs() {
|
|
local out="" seg rest="$1/"
|
|
while [ -n "$rest" ]; do
|
|
seg="${rest%%/*}"
|
|
rest="${rest#*/}"
|
|
case "$seg" in
|
|
""|".") ;;
|
|
"..") out="${out%/*}" ;;
|
|
*) out="$out/$seg" ;;
|
|
esac
|
|
done
|
|
printf '%s\n' "${out:-/}"
|
|
}
|
|
|
|
# Make paths canonical and workspace-relative. Paths that resolve outside
|
|
# the workspace are not covered by .clineignore, so they pass through.
|
|
root=$(normalize_abs "$root")
|
|
rel_paths=""
|
|
while IFS= read -r p; do
|
|
[ -n "$p" ] || continue
|
|
case "$p" in
|
|
/*) ;;
|
|
*) p="$root/$p" ;;
|
|
esac
|
|
p=$(normalize_abs "$p")
|
|
case "$p" in
|
|
"$root"/*) rel_paths+="${p#"$root"/}"$'\n' ;;
|
|
esac
|
|
done <<< "$paths"
|
|
|
|
if [ -z "$rel_paths" ]; then
|
|
echo '{"cancel": false}'
|
|
exit 0
|
|
fi
|
|
|
|
# The guard is only as strong as the ignore file itself: protect
|
|
# .clineignore from modification so the agent cannot un-ignore files.
|
|
if [ "$tool" != "read_files" ] && printf '%s' "$rel_paths" | grep -qx '\.clineignore'; then
|
|
jq -n --arg tool "$tool" \
|
|
'{cancel: true, errorMessage: "Blocked \($tool): modifying .clineignore is not allowed. Update it yourself if a file should be un-ignored."}'
|
|
exit 0
|
|
fi
|
|
|
|
# Evaluate the paths against .clineignore with real gitignore semantics via
|
|
# `git check-ignore`. Running it inside an empty scratch repo scopes the
|
|
# check to .clineignore alone -- the workspace's own .gitignore files are
|
|
# never consulted, and this works even outside a git repository.
|
|
scratch=$(mktemp -d)
|
|
trap 'rm -rf "$scratch"' EXIT
|
|
git init -q "$scratch"
|
|
|
|
blocked=$(printf '%s' "$rel_paths" \
|
|
| git -C "$scratch" -c core.excludesFile="$ignore_file" check-ignore --stdin --no-index 2>/dev/null \
|
|
| paste -sd, -)
|
|
|
|
if [ -n "$blocked" ]; then
|
|
jq -n --arg tool "$tool" --arg files "$blocked" \
|
|
'{cancel: true, errorMessage: "Blocked \($tool): \($files) matched a .clineignore pattern, so Cline may not access it. Update .clineignore if access should be allowed."}'
|
|
else
|
|
echo '{"cancel": false}'
|
|
fi
|
|
```
|
|
</Accordion>
|
|
|
|
The source lives at [`sdk/examples/hooks/PreToolUse_ClineignoreGuard.sh`](https://github.com/cline/cline/blob/main/sdk/examples/hooks/PreToolUse_ClineignoreGuard.sh).
|
|
|
|
<Note>
|
|
This hook covers only part of what `.clineignore` did — and enforces more than it ever did:
|
|
- It **blocks file reads, edits, and shell commands** — it does **not** filter search or file-listing results, so it is an access-control boundary rather than a context-reduction tool (the original `.clineignore` was the reverse).
|
|
- A cancelled tool call stops the current task run with the reason shown; send a follow-up message to continue the conversation.
|
|
- The shell guard is a conservative token check, not a full shell parser: it catches straightforward access like `cat .env`, but a sufficiently creative command could still slip through. Paths are canonicalized lexically, but symlinks are not resolved — a pre-existing symlink aliasing an ignored file is not caught, so add such aliases to `.clineignore` too.
|
|
- In the CLI, hooks are disabled in `--yolo` mode; use `--act` or `--plan`.
|
|
</Note>
|
|
|
|
---
|
|
|
|
<Info>
|
|
The rest of this page is the original `.clineignore` reference, retained for existing users while the feature is phased out.
|
|
</Info>
|
|
|
|
The `.clineignore` file tells Cline which files and directories to skip when analyzing your codebase. It works like `.gitignore`: create a file named `.clineignore` in your project root, add patterns for files you want excluded, and Cline will ignore them.
|
|
|
|
|
|
|
|
## Why It Matters
|
|
|
|
Without a `.clineignore`, Cline may load your entire project into context, including dependencies, build artifacts, and generated files. This wastes tokens, increases costs, and can push useful context out of the window.
|
|
|
|
Adding a `.clineignore` can cut your starting context from 200k+ tokens to under 50k. That means faster responses, lower costs, and the ability to use smaller, cheaper models effectively.
|
|
|
|
## Creating a .clineignore
|
|
|
|
Create a file named `.clineignore` in your project root:
|
|
|
|
```text
|
|
# Dependencies
|
|
node_modules/
|
|
**/node_modules/
|
|
|
|
# Build outputs
|
|
/build/
|
|
/dist/
|
|
/.next/
|
|
/out/
|
|
|
|
# Testing artifacts
|
|
/coverage/
|
|
|
|
# Environment variables
|
|
.env
|
|
.env.*
|
|
|
|
# Large data files
|
|
*.csv
|
|
*.xlsx
|
|
*.sqlite
|
|
|
|
# Generated/minified code
|
|
*.min.js
|
|
*.map
|
|
```
|
|
|
|
## Pattern Syntax
|
|
|
|
`.clineignore` uses the same pattern syntax as `.gitignore`:
|
|
|
|
| Pattern | Matches |
|
|
|---------|---------|
|
|
| `node_modules/` | The `node_modules` directory |
|
|
| `**/node_modules/` | `node_modules` at any depth |
|
|
| `*.csv` | All CSV files |
|
|
| `/build/` | The `build` directory at the project root only |
|
|
| `*.env.*` | Files like `.env.local`, `.env.production` |
|
|
| `!important.csv` | Exception: do not ignore this file |
|
|
|
|
Lines starting with `#` are comments. Blank lines are ignored.
|
|
|
|
## What to Exclude
|
|
|
|
Start with these categories and adjust for your project:
|
|
|
|
**Almost always exclude:**
|
|
- Package manager directories (`node_modules/`, `vendor/`, `.venv/`)
|
|
- Build outputs (`dist/`, `build/`, `.next/`, `out/`)
|
|
- Coverage reports (`coverage/`)
|
|
- Lock files if large (`package-lock.json`, `yarn.lock`)
|
|
|
|
**Exclude if present:**
|
|
- Large data files (`.csv`, `.xlsx`, `.sqlite`, `.parquet`)
|
|
- Binary assets (images, fonts, videos)
|
|
- Generated code (API clients, protobuf outputs, minified bundles)
|
|
- Environment files with secrets (`.env`, `.env.local`)
|
|
|
|
**Keep accessible:**
|
|
- Source code you actively work on
|
|
- Configuration files Cline needs to understand (`tsconfig.json`, `package.json`)
|
|
- Documentation and READMEs
|
|
- Test files (Cline often needs these for context)
|
|
|
|
## How It Works
|
|
|
|
When Cline scans your project to build context, it checks each file path against your `.clineignore` patterns. Matching files are excluded from:
|
|
|
|
- The file listing Cline sees when starting a task
|
|
- Automatic context gathering during conversations
|
|
- Search results when Cline looks for relevant code
|
|
|
|
As noted above, explicit [@ mentions](/core-workflows/working-with-files) still bypass these rules — for example, `@/node_modules/some-package/index.js` reads that file even though `node_modules/` is ignored. Ignore rules control automatic loading, not explicit access.
|
|
|
|
<Note>
|
|
`.clineignore` is separate from `.gitignore`. Files tracked by Git but irrelevant to Cline (like large test fixtures or data files) should go in `.clineignore` even if they're not in `.gitignore`.
|
|
</Note>
|
|
|
|
## Tips
|
|
|
|
- Check your token usage in the task header after adding a `.clineignore`. The difference is often dramatic.
|
|
- If Cline seems to be missing context about a file, check whether it's being excluded by your ignore patterns.
|
|
- For monorepos or multi-root workspaces, each workspace root can have its own `.clineignore`. See [Multi-Root Workspaces](/features/multiroot-workspace) for details.
|
|
|
|
## Related
|
|
|
|
|
|
- [Cline Rules](/customization/cline-rules) - Define persistent instructions for Cline
|
|
- [Task Management](/core-workflows/task-management#context-window) - Understand how context windows work
|
|
- [Auto-Compact](/features/auto-compact) - Automatic context compression during long tasks
|