Skip to content

ToolValidator and AutoApprove: Two Gates Before Execution

源码版本v4.0.10

Responsibilities

Before a tool actually runs, two independent checks fire: ToolValidator enforces parameter and path legality, and AutoApprove decides whether to pop an approval dialog. Neither executes the tool — they only decide "can it run" and "should the user be asked".

ToolValidator is a very thin utility class for handlers. It exposes two methods: assertRequiredParams checks that required parameters are present, and checkClineIgnorePath checks whether a path is blocked by .clineignore (assertRequiredParams:17). It has no side effects — it just returns a ValidationResult and lets the handler decide what to do.

AutoApprove is slightly more involved. It is a private field of ToolExecutor, injected into TaskConfig via asToolConfig (autoApprover init:126). Handlers reach it indirectly through config.callbacks.shouldAutoApproveTool or config.callbacks.shouldAutoApproveToolWithPath (callback wiring:181). It reads three global switches — yoloModeToggled, autoApproveAllToggled, autoApprovalSettings — plus whether the path is inside the workspace, and produces one of three outcomes: "local auto-approve", "external auto-approve", or "no auto-approve".

Design motivation

  • Validator and AutoApprove are separated: legality checks and approval are orthogonal concerns — "parameters valid but user has not approved" and "parameters invalid but user already approved" can both occur. Splitting them into two objects means a handler need not skip approval on an error path, and can still block invalid parameters even after approval passes.
  • Validator only looks, never mutates: it returns a ValidationResult rather than throwing, so the handler decides whether to push an error result or fall back to a degraded path (ValidationResult:4). Different handlers can then share the same error display style.
  • Three tiers of AutoApprove switches: yolo lets everything through, autoApproveAll is the next-broader scope, and autoApprovalSettings is the most granular (distinguishing local/external, safe commands/all commands) (shouldAutoApproveTool:42). The broader switch wins.
  • Path classification gates external access: for read/write tools the auto-approve result is a [local, external] tuple (tuple result:96). local controls files inside the workspace, external controls files outside. By default external is stricter, to prevent accidental edits to system files.
  • Workspace info is cached at task scope: getWorkspaceInfo is fetched once and cached in workspacePathsCache and isMultiRootScenarioCache (getWorkspaceInfo:23). Within a task's lifetime the workspace roots rarely change, so caching avoids repeated IPC calls.

Key files

Data flow

After receiving a block, a handler typically uses Validator to check parameters first, then AutoApprove to pick the UI path. Here is the actual call WriteToFileToolHandler makes during the partial-block stage:

typescript
// apps/vscode/src/core/task/tools/handlers/WriteToFileToolHandler.ts
// Handle auto-approval vs manual approval for partial
if (await uiHelpers.shouldAutoApproveToolWithPath(block.name, relPath)) {
    await uiHelpers.removeLastPartialMessageIfExistsWithType("ask", "tool") // in case the user changes auto-approval settings mid stream
    await uiHelpers.say("tool", partialMessage, undefined, undefined, block.partial)
} else {
    await uiHelpers.removeLastPartialMessageIfExistsWithType("say", "tool")
    await uiHelpers.ask("tool", partialMessage, block.partial).catch(() => {})
}

Inside shouldAutoApproveToolWithPath, the two master switches — YOLO and autoApproveAll — are checked first; if either is on, the function returns true immediately (yolo short circuit:126). It then determines whether the path is inside the workspace and combines that with the tuple from shouldAutoApproveTool to produce the final decision:

typescript
// apps/vscode/src/core/task/tools/autoApprove.ts
const autoApproveResult = this.shouldAutoApproveTool(blockname)
const [autoApproveLocal, autoApproveExternal] = Array.isArray(autoApproveResult)
    ? autoApproveResult
    : [autoApproveResult, false]

if ((isLocalRead && autoApproveLocal) || (!isLocalRead && autoApproveLocal && autoApproveExternal)) {
    return true
}
return false

Here autoApproveExternal is only checked when autoApproveLocal is also true — meaning "auto-approve external" implicitly requires "auto-approve local". This is Cline's default safety posture: external operations are always one tier stricter than local ones.

Boundaries and failure modes

  • Empty strings count as missing: assertRequiredParams uses String(val).trim() === "", so empty strings and whitespace-only strings all count as missing (empty check:21). But the content field is allowed to be an empty string (creating an empty file), so the handler judges that itself with == null and does not go through Validator (content null check:42).
  • No path defaults to no approval: when shouldAutoApproveToolWithPath receives an undefined path, it sets isLocalRead = false directly (no path default:153). The safe-side default — a tool without a path is treated as an external operation.
  • The cache assumes the workspace does not change: the comment says it outright — "assumes that the task has a fixed set of workspace roots" (cache assumption:11). If the user adds a new root mid-task, this task still sees the old cache; the new root only takes effect for the next task.
  • YOLO does not cover every tool: the YOLO switch only lists read/write/bash/browser/web/MCP; ASK, ATTEMPT, and NEW_TASK are not listed (yolo switch list:44). These tools fall through to return false and require normal approval.
  • The YOLO tuple only applies to file/bash tools: the [true, true] tuple is only returned for read/write/bash/subagents (yolo tuple:55). browser/web/MCP receive a scalar true, meaning that under YOLO they do not distinguish local from external.
  • Validator is not used by the legacy ToolExecutor: the class comment says it explicitly — "The legacy ToolExecutor switch remains unchanged and does not depend on this" (legacy note:8). In the legacy path, parameter checks are scattered across the case branches; Validator only serves the new handlers.

Summary

Validator is the hard check on parameters and paths — on failure it surfaces an error and lets the model retry. AutoApprove is a soft, user-preference-driven switch that decides between auto-approval and a dialog. Together they act as gatekeepers before a tool executes; handlers call them and then decide the next move. To see how the handler list is registered, see /tools/handlers-overview; for the router itself, see /tools/coordinator.

See official docs: Cline 文档 · README