跳到正文
FunCoding

搜索

搜索文档、文章、Skill 和 MCP

.clineignore (deprecate soon)

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

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

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:

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):

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:

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

{
  "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.

PreToolUse_ClineignoreGuard.sh (full script)
#!/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

The source lives at sdk/examples/hooks/PreToolUse_ClineignoreGuard.sh.

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.

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

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:

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

PatternMatches
node_modules/The node_modules directory
**/node_modules/node_modules at any depth
*.csvAll CSV files
/build/The build directory at the project root only
*.env.*Files like .env.local, .env.production
!important.csvException: 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 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.

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

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 for details.