> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cline.bot/llms.txt
> Use this file to discover all available pages before exploring further.

# .clineignore (deprecate soon)

> 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
# .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 theme={"system"}
{
  "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 theme={"system"}
  #!/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 theme={"system"}
# 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
