Skip to content

Command and file-read tools: ExecuteCommand / ReadFile / SearchFiles / ListFiles

源码版本v4.0.10

Responsibilities

This page covers four "capability" read-only/execution tools in Cline, all registered under the IFullyManagedTool interface and uniformly dispatched by ToolExecutor:

  • ExecuteCommandToolHandler (ClineDefaultTool.BASH): runs a shell command in the terminal, with timeout, multi-workspace, and permission checks (class declaration:58).
  • ReadFileToolHandler (ClineDefaultTool.FILE_READ): reads a single file, adds 1-based line numbers, supports start_line/end_line slicing, and returns an image block for image files (class declaration:142).
  • SearchFilesToolHandler (ClineDefaultTool.SEARCH): regex search based on ripgrep, supports parallel search across multiple workspaces and file_pattern filtering (class declaration:21).
  • ListFilesToolHandler (ClineDefaultTool.LIST_FILES): lists a directory, supports recursion, truncates at a 200-entry cap (class declaration:17).

These four tools share one interaction pattern: handlePartialBlock pushes UI early so the user sees "the model is reading X", while execute does validation (clineignore + path + parameters) → approval → PreToolUse hook → actual execution → telemetry. They all respect config.isSubagentExecution: during subagent execution they skip parts of the UI push and ask flow and just get to work.

The fundamental difference from WriteToFileToolHandler is "read-only or external side effects" — read, list, search do not change filesystem state; execute_command changes state but goes through the terminal and never enters the diff view, so this family of tools has no diff / userEdits reflow path.

Design motivation

  • Unified mistake-count rule: every tool does consecutiveMistakeCount++ on missing parameters or clineignore hits, and = 0 after the core operation succeeds, so YOLO mode's consecutive-error quota accumulates across tools (reset counter:377).
  • Command-classified timeout: execute_command matches common long-running commands (npm install, cargo build, pytest, etc.) to a 300-second timeout, 30 seconds otherwise, so short commands are not held back by a long timeout (LONG_RUNNING patterns:22-34).
  • Dual permission gates: the CLINE_COMMAND_PERMISSIONS env-var segment-level allowlist plus the clineignore path check — two independent rejection layers (permission checks:162-190).
  • Read-file dedup cache: fileReadCache stores only metadata (readCount + mtime + imageBlock), not content; on a hit with unchanged mtime it returns a "already read" hint directly, preventing the model from burning tokens rereading the same file (dedup cache:298-314).
  • Line-numbered display: formatFileContentWithLineNumbers prefixes each line with N | and appends a continuation hint, letting the model continue reading precisely via start_line (formatFileContentWithLineNumbers:79).
  • Multi-workspace parallel search: in multi-workspace mode SearchFilesToolHandler runs all searchPaths in parallel via Promise.all, then buckets and aggregates by workspace name (parallel search:279-285).
  • ListFiles 200 cap: listFiles's third argument limit=200; once exceeded it returns didHitLimit=true, and formatFilesList appends a hint prompting the model to narrow scope with a more specific path (listFiles call:100).

Key files

  • LONG_RUNNING patterns:22-34 — regex array of long-running commands: npm/cargo/pytest/docker build/torchrun, etc.
  • timeout resolution:36-56isLikelyLongRunningCommand + resolveCommandTimeoutSeconds, preferring the timeout parameter supplied by the model.
  • ExecuteCommand class:58name = ClineDefaultTool.BASH; handlePartialBlock waits for the complete block before saying when auto-approved.
  • workspace hint:135-159 — parses the @workspace:command prefix, redirecting executionDir to the corresponding workspace root.
  • permission checks:162-190commandPermissionController.validateCommand + clineIgnoreController.validateCommand.
  • approval flow:223-279 — auto-approve requires !requiresApprovalPerLLM && autoApproveSafe or, for risky commands, autoApproveSafe && autoApproveAll (dual gate).
  • cache clear:319-327 — after the command runs, fileReadCache.clear() is invoked wholesale, because sed/git checkout/mv may unpredictably modify files.
  • formatFileContentWithLineNumbers:79 — slices, adds line numbers, appends "Showing lines X-Y of Z" hint.
  • ReadFile class:142name = ClineDefaultTool.FILE_READ.
  • dedup cache:298-356 — mtime comparison + readCount increment; from the 3rd read onward returns a DUPLICATE READ warning.
  • extract + cache:359-373 — calls extractFileContent (images return an imageBlock); on failure emits a tool error rather than throwing to the upper layer.
  • determineSearchPaths:35 — expands search paths by hint or across all workspaces in multi-workspace mode.
  • executeSearch:74 — calls regexSearchFiles (ripgrep), filters results with clineIgnoreController.
  • formatSearchResults:120 — buckets by workspace name in multi-workspace mode, returns directly in single-workspace mode.
  • listFiles call:100listFiles(absolutePath, recursive, 200) returns [files, didHitLimit].

Data flow

ExecuteCommand is the most complex of the four; its core is the approval gate and timeout resolution. The model's requires_approval + command string decides which approval path to take:

typescript
// apps/vscode/src/core/task/tools/handlers/ExecuteCommandToolHandler.ts
timeoutSeconds = resolveCommandTimeoutSeconds(
    command,
    timeoutParam,
    config.yoloModeToggled || config.vscodeTerminalExecutionMode === "backgroundExec",
)
// ...
if (
    config.isSubagentExecution ||
    (!requiresApprovalPerLLM && autoApproveSafe) ||
    (requiresApprovalPerLLM && autoApproveSafe && autoApproveAll)
) {
    // Auto-approve flow
    await config.callbacks.removeLastPartialMessageIfExistsWithType("ask", "command")
    await config.callbacks.say("command", actualCommand, undefined, undefined, false)
    didAutoApprove = true
} else {
    // Manual approval flow
    const didApprove = await ToolResultUtils.askApprovalAndPushFeedback(
        "command",
        actualCommand + `${autoApproveSafe && requiresApprovalPerLLM ? COMMAND_REQ_APP_STRING : ""}`,
        config,
    )
    if (!didApprove) {
        return formatResponse.toolDenied()
    }
}

autoApprover.shouldAutoApproveTool returns a [autoApproveSafe, autoApproveAll] tuple: the first means "auto-approve safe commands", the second "auto-approve all commands". When the model says risky (requires_approval=true) and only safe auto-approve is configured, it goes through manual approval and appends the strong-approval prompt string to the command (auto-approve logic:223-227). After approval: PreToolUse hook, then a 30-second notification timer (set only when auto-approved and notifications are on), and finally executeCommandTool(finalCommand, timeoutSeconds) runs in the terminal. After completion the entire fileReadCache is cleared.

The core of ReadFileToolHandler is cache-based deduplication:

typescript
// apps/vscode/src/core/task/tools/handlers/ReadFileToolHandler.ts
const cacheKey = absolutePath.toLowerCase()
const cached = config.taskState.fileReadCache.get(cacheKey)
if (cached) {
    try {
        const stat = await import("node:fs/promises").then((fs) => fs.stat(absolutePath))
        if (stat.mtimeMs !== cached.mtime) {
            config.taskState.fileReadCache.delete(cacheKey)
        }
    } catch {
        config.taskState.fileReadCache.delete(cacheKey)
    }
}

If mtime changed (the user hand-edited in the editor), evict; otherwise reuse the cache. The cache stores no content to save memory; on a hit the file is still re-read from disk. Once readCount reaches 3 or more, a DUPLICATE READ strong hint is returned. After a fresh successful read, extractFileContent retrieves the content (image files return an imageBlock pushed to userMessageContent), and mtime + readCount=1 are stored in the cache.

SearchFiles and ListFiles both do path resolution + clineignore + approval + execution + formatting, with similar code structures. SearchFiles adds multi-workspace parallelism:

typescript
// apps/vscode/src/core/task/tools/handlers/SearchFilesToolHandler.ts
const searchPromises = searchPaths.map(({ absolutePath, workspaceName, workspaceRoot }) =>
    this.executeSearch(config, absolutePath, workspaceName, workspaceRoot, regex, filePattern),
)
const searchResults = await Promise.all(searchPromises)
const results = this.formatSearchResults(config, searchResults, searchPaths)

regexSearchFiles calls the underlying ripgrep, and results are bucketed and aggregated by workspace name.

Boundaries and failure modes

  • Missing parameters: execute_command missing command / requires_approval, read/search/list missing path, search missing regex — all do consecutiveMistakeCount++ and go through sayAndCreateMissingParamError (missing params:100-112).
  • Command denied: when CLINE_COMMAND_PERMISSIONS rejects, returns permissionDeniedError carrying failedSegment or matchedPattern so the model has corrective information (permission denied:162-181).
  • Long-running command notification: when auto-approve + notifications are on, a system notification "Command is still running" fires after 30 seconds so the user does not think it is stuck (timeout notification:294-303).
  • Read file does not exist: extractFileContent throws are caught and turned into toolError("Error reading file: ..."), not propagated to the Task layer, letting the model see the error and adjust the path itself (read error:361-373).
  • Cache mtime mismatch: if stat fails or mtime changed, the cache is evicted so the next read sees the latest content (mtime check:304-313).
  • Search path resolution failure: when parseWorkspaceInlinePath throws, returns Error resolving search path: ... and mistake++ (path resolution error:233-242).
  • list files exceeds cap: listFiles throws or returns Error listing files: ... for a bad path; after success, didHitLimit is turned into a hint by formatFilesList (list error:101-105).
  • Subagent skips UI: all four handlers skip partial say and ask flows when isSubagentExecution is true, just doing the work and returning (subagent skip:155-157).

Summary

These four handlers are Cline's "eyes and hands": ExecuteCommand runs shell, ReadFile reads disk, SearchFiles runs ripgrep, ListFiles lists directories. They share the "validate → approve → hook → execute → telemetry" skeleton; the differences are in execution details: execute_command has command-classified timeouts and dual-gate approval, read_file has an mtime dedup cache, search supports multi-workspace parallelism, and list has a 200-entry cap. Every tool resets consecutiveMistakeCount on success and accumulates failures until the YOLO cap stops them.

To dig deeper, continue with:

  • Disk-writing tools: /edit-tools/write-to-file
  • Multi-file patches: /edit-tools/apply-patch
  • Browser tools: /cap-tools/browser
  • Web fetch/search: /cap-tools/web

See official docs: Cline 文档 · README