Command and file-read tools: ExecuteCommand / ReadFile / SearchFiles / ListFiles
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= 0after 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_PERMISSIONSenv-var segment-level allowlist plus the clineignore path check — two independent rejection layers (permission checks:162-190). - Read-file dedup cache:
fileReadCachestores 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:
formatFileContentWithLineNumbersprefixes each line withN |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-56—isLikelyLongRunningCommand+resolveCommandTimeoutSeconds, preferring the timeout parameter supplied by the model.ExecuteCommand class:58—name = ClineDefaultTool.BASH; handlePartialBlock waits for the complete block before saying when auto-approved.workspace hint:135-159— parses the@workspace:commandprefix, redirecting executionDir to the corresponding workspace root.permission checks:162-190—commandPermissionController.validateCommand+clineIgnoreController.validateCommand.approval flow:223-279— auto-approve requires!requiresApprovalPerLLM && autoApproveSafeor, 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:142—name = 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— callsextractFileContent(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— callsregexSearchFiles(ripgrep), filters results with clineIgnoreController.formatSearchResults:120— buckets by workspace name in multi-workspace mode, returns directly in single-workspace mode.listFiles call:100—listFiles(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:
// 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:
// 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:
// 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 throughsayAndCreateMissingParamError(missing params:100-112). - Command denied: when
CLINE_COMMAND_PERMISSIONSrejects, returnspermissionDeniedErrorcarrying 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:
extractFileContentthrows are caught and turned intotoolError("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
parseWorkspaceInlinePaththrows, returnsError resolving search path: ...and mistake++ (path resolution error:233-242). - list files exceeds cap:
listFilesthrows or returnsError listing files: ...for a bad path; after success, didHitLimit is turned into a hint byformatFilesList(list error:101-105). - Subagent skips UI: all four handlers skip partial say and ask flows when
isSubagentExecutionis 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