docs/sdk/guides/permission-handling.mdx
Tool policies control whether tools are enabled and whether they run without approval. Tool names not listed in toolPolicies default to enabled and auto-approved, so set policies explicitly for tools that need review.
The simplest way to manage permissions is through tool policies. Set them per-tool when creating an agent:
const agent = new Agent({
// ...config
tools: [readFileTool, writeFileTool, bashTool, searchTool],
toolPolicies: {
read_files: { autoApprove: true }, // Always run without asking
search_codebase: { autoApprove: true }, // Always run without asking
write_file: { autoApprove: false }, // Always ask before running
run_commands: { autoApprove: false }, // Always ask before running
},
})
| Policy | Effect |
|---|---|
{ autoApprove: true } | Tool executes immediately without approval |
{ autoApprove: false } | Tool waits for approval before executing |
{ enabled: false } | Tool is completely disabled (model won't see it) |
| No policy set | Defaults to enabled and auto-approved |
For trusted environments (CI pipelines, sandboxed containers, development scripts):
const agent = new Agent({
// ...config
tools: allTools,
toolPolicies: Object.fromEntries(
allTools.map((t) => [t.name, { autoApprove: true }])
),
})
Or with ClineCore:
const session = await cline.start({
config: {
enableTools: true,
},
toolPolicies: {
run_commands: { autoApprove: true },
editor: { autoApprove: true },
read_files: { autoApprove: true },
apply_patch: { autoApprove: true },
search_codebase: { autoApprove: true },
fetch_web_content: { autoApprove: true },
},
capabilities: {
requestToolApproval: async () => ({ approved: true }),
},
// ...
})
For applications that need human oversight, implement a custom approval handler:
import { ClineCore } from "@cline/sdk"
import * as readline from "readline"
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
const ask = (q: string) => new Promise<string>((res) => rl.question(q, res))
const cline = await ClineCore.create({
clientName: "interactive-app",
capabilities: {
requestToolApproval: async (request) => {
console.log(`\nTool: ${request.toolName}`)
console.log(`Input: ${JSON.stringify(request.input, null, 2)}`)
const answer = await ask("Approve? (y/n): ")
return { approved: answer.toLowerCase() === "y" }
},
},
})
A practical middle ground: auto-approve read-only operations, require approval for writes:
const READ_TOOLS = new Set(["read_files", "search_codebase", "fetch_web_content"])
const WRITE_TOOLS = new Set(["run_commands", "editor", "apply_patch"])
const toolPolicies: Record<string, { autoApprove: boolean }> = {}
for (const tool of READ_TOOLS) {
toolPolicies[tool] = { autoApprove: true }
}
for (const tool of WRITE_TOOLS) {
toolPolicies[tool] = { autoApprove: false }
}
Approve based on what the tool is actually doing, not just which tool it is:
const cline = await ClineCore.create({
clientName: "smart-approval",
capabilities: {
requestToolApproval: async (request) => {
// Auto-approve non-destructive shell commands
if (request.toolName === "run_commands") {
const cmd = JSON.stringify(request.input)
const safeCommands = ["ls", "cat", "grep", "find", "git status", "git log", "git diff"]
if (safeCommands.some((safe) => cmd.startsWith(safe))) {
return { approved: true }
}
}
// Auto-approve reads to specific directories
if (request.toolName === "read_files") {
const path = request.input.path as string
if (path.startsWith("/src/") || path.startsWith("/tests/")) {
return { approved: true }
}
}
// Everything else requires manual approval
console.log(`Approval needed: ${request.toolName}`)
console.log(`Input: ${JSON.stringify(request.input)}`)
return { approved: false }
},
},
})
When approval is denied, the agent receives a rejection message and can adjust its approach. It might:
The agent does not get stuck in a loop. The rejection counts as a response, and the agent proceeds with its next iteration.