Skip to content

fix(agent): register built-in tools from --allowedTools#1267

Open
josephfinlayson wants to merge 1 commit intoanthropics:mainfrom
josephfinlayson:fix/agent-mode-register-built-in-tools
Open

fix(agent): register built-in tools from --allowedTools#1267
josephfinlayson wants to merge 1 commit intoanthropics:mainfrom
josephfinlayson:fix/agent-mode-register-built-in-tools

Conversation

@josephfinlayson
Copy link
Copy Markdown

Symptom

After this PR, init.tools reports the actual built-in tool set requested via --allowedTools instead of always defaulting to [Bash, Read] in agent mode. Today the model literally cannot call Edit / Glob / Grep / Write / MultiEdit / LS even when those names are passed via claude_args: --allowedTools ….

Before / after

Run with claude_args: --allowedTools 'Bash(git:*),Edit,Read,Glob,Grep,Write':

Before

init: { tools: ["Bash", "Read"], … }

The model has no way to call Edit, Glob, Grep, or Write.

After

init: { tools: ["Bash", "Edit", "Read", "Glob", "Grep", "Write"], … }

Registration matches what the user asked for.

Root cause

The SDK's Options.tools field is the registration knob — it controls which built-in tools are wired into the spawned CLI session. Options.allowedTools is a separate, downstream auto-approve gate-list. parseSdkOptions constructed sdkOptions with model, maxTurns, allowedTools, disallowedTools, systemPrompt, fallbackModel, pathToClaudeCodeExecutable, extraArgs, env, settingSources — but never tools. With tools undefined, the SDK falls through to a reduced default in agent mode and the model only sees [Bash, Read].

Issue #690 has this analysis (verbatim):

While Agent mode has parseAllowedTools() function in src/modes/agent/parse-tools.ts that can parse --allowedTools from claude_args, this parsed value is: Used only in prepareMcpConfig(), Not returned by mode.getAllowedTools(), Not available during the buildDisallowedToolsString() call.

The fix belongs in the option-parsing layer because both agent and tag modes funnel through parseSdkOptions.

Maintainer-blessed pattern that this PR makes work

examples/ci-failure-auto-fix.yml advertises:

claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(gh:*)'"

Today that string only sets the auto-approve gate-list — the model never actually receives Edit/MultiEdit/Write/Glob/Grep/LS. After this PR, that example does what users (and the docs) expect.

What changed

base-action/src/parse-sdk-options.ts:

  1. New BUILT_IN_TOOL_NAMES constant: the SDK-known base tools (Bash, Read, Write, Edit, MultiEdit, Glob, Grep, LS, NotebookRead, NotebookEdit, Task, TodoWrite, WebFetch, WebSearch).
  2. New extractBuiltInTools(mergedAllowedTools) helper that:
    • strips any (…) suffix (Bash(git:*)Bash),
    • drops mcp__* entries (those are MCP tool names, not base tools),
    • keeps only entries whose stem is in BUILT_IN_TOOL_NAMES,
    • dedupes while preserving input order,
    • returns undefined if nothing matches (so we leave Options.tools unset and the SDK keeps its current default — preserving prior behaviour for that case).
  3. Binds the result to sdkOptions.tools in the existing sdkOptions object literal. sdkOptions.allowedTools is unchanged — the auto-approve gate-list still carries the original entries (Bash patterns, MCP names, etc.) so MCP tool gating and Bash(git:*)-style patterns keep working.

Backwards compatibility

  • If --allowedTools only contains MCP/Bash(...) entries (after stem filtering produces no built-ins), Options.tools stays undefined and behaviour is unchanged.
  • Users currently relying on the broken default will see init.tools expand to what they asked for, not contract — they always wanted those tools; the auto-approve gate matched the same names, the registration just never followed.
  • No changes to extraArgs, MCP config merging, or tag/agent mode files.

Tests

Added six cases to base-action/test/parse-sdk-options.test.ts under a new built-in tool registration (sdkOptions.tools) describe block:

  1. --allowedTools Edit,Read,Glob,Greptools = ["Edit","Read","Glob","Grep"].
  2. --allowedTools Bash(git:*),Edit,Read,mcp__github_comment__update_claude_commenttools = ["Bash","Edit","Read"]; allowedTools keeps the original entries.
  3. No --allowedToolstools undefined.
  4. --allowedTools with only MCP entries → tools undefined; allowedTools keeps the MCP entries.
  5. --allowedTools Edit,Edit,Readtools = ["Edit","Read"] (deduped).
  6. --allowedTools Foobar,Readtools = ["Read"] (unknown stem dropped).
  7. Plus a merge test combining claudeArgs --allowedTools and direct options.allowedTools.

bun test → 672 pass / 0 fail (34 pass / 0 fail in parse-sdk-options.test.ts).
bun run typecheck → clean.
bun run format:check → clean.

Fixes / relates to

Fixes #690. Addresses the docs-vs-behaviour gap users hit in #181, #533, and #264.

…cts actual toolset

Today --allowedTools sets the auto-approve gate-list (Options.allowedTools)
but never sets Options.tools, so the SDK's spawned CLI defaults init.tools
to the reduced set [Bash, Read] in agent mode. The model literally cannot
call Edit/Glob/Grep/Write even when allow-listed.

Bind built-in tool names from --allowedTools to Options.tools so the
registered toolset matches what the user requested. MCP tool names
(mcp__*) and Bash() pattern entries are filtered out of the registration
list (they aren't base tools) but stay in allowedTools as before.

Fixes anthropics#690, anthropics#181 (in part), and the docs vs. behaviour gap users hit in
anthropics#533 and anthropics#264.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--allowedTools in claude_args doesn't prevent default disabling of WebSearch and WebFetch

1 participant