# .clineignore (deprecate soon)

> Control which files and directories Cline can access in your project. This feature will be deprecated soon.

- 网址：https://funcoding.ai/agents/cline/customization/clineignore/
- 来源：Cline 官方文档原文（英文），Apache-2.0 许可，同步于 2026-10-11
- 官方原文：https://docs.cline.bot/customization/clineignore

---
<div class="callout callout-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.

</div>

## 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.

<details>
<summary>PreToolUse_ClineignoreGuard.sh (full script)</summary>

```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
```

</details>

The source lives at [`sdk/examples/hooks/PreToolUse_ClineignoreGuard.sh`](https://github.com/cline/cline/blob/main/sdk/examples/hooks/PreToolUse_ClineignoreGuard.sh).

<div class="callout callout-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`.

</div>

---

<div class="callout callout-note">

The rest of this page is the original `.clineignore` reference, retained for existing users while the feature is phased out.

</div>

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](https://funcoding.ai/agents/cline/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.

<div class="callout callout-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`.

</div>

## 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](https://funcoding.ai/agents/cline/features/multiroot-workspace/) for details.

## Related

- [Cline Rules](https://funcoding.ai/agents/cline/customization/cline-rules/) - Define persistent instructions for Cline
- [Task Management](https://docs.cline.bot/core-workflows/task-management#context-window) - Understand how context windows work
- [Auto-Compact](https://docs.cline.bot/features/auto-compact) - Automatic context compression during long tasks
