Skip to content

fix(subagents): admit tools extensions register at session_start (#883) - #1115

Open
agegr wants to merge 1 commit into
mainfrom
fix/subagent-late-tools
Open

agegr wants to merge 1 commit into
mainfrom
fix/subagent-late-tools

Conversation

@agegr

@agegr agegr commented Oct 8, 2026

Copy link
Copy Markdown
Owner

A child got a tools allowlist of the extension tools registered when it was built. The SDK (still in 1.1.0) fixes that list at session creation and offers no source-aware admission hook, so a tool an extension registers at session_start or later (pi's examples/extensions/dynamic-tools.ts) was never callable in a subagent, even with extensions loaded: the model was never offered it and a forced call returned Tool echo_session not found. Reproduced on main with the reporter's offline repro adapted to the real spawn path.

Fixed for children that load extensions without ext: selectors (the Agents panel's extensions switch, or an extensions: [...] list):

  • subagentToolOptions() in lib/subagents.ts, shared by spawn (lib/subagent-runtime.ts) and reopen (lib/rpc-manager.ts): no allowlist; noTools: "builtin", the profile's built-ins as +name entries, and the other built-ins plus the reserved subagent tools excluded. An extension still cannot switch on a built-in the profile leaves out (tested: an extension enabling write fails).
  • The snapshot records allExtensionTools, so reload, resume and reopen behave the same. Child sessions created before this keep their old allowlist on reopen.
  • No private SDK fields. SDK_BUILTIN_TOOLS mirrors the SDK's unexported built-in list; a test catches a new built-in.

Not fixed: children with ext: selectors still resolve to tool names at spawn, so a selected extension's late tools stay unavailable. Workaround: scope with extensions: [dynamic-tools] instead of tools: ext:dynamic-tools. Choosing late tools one by one needs an SDK hook.

Tests: lib/subagent-late-tools.integration.test.mjs (real AgentSession, faux provider: spawn, reload, reopen, the extensions: list case, the non-matching selector), a unit test in lib/subagents.test.mjs, updated source patterns in lib/rpc-manager.test.mjs. tsc, eslint, npm test pass.

Refs #883

🤖 Generated with Claude Code

A child got a `tools` allowlist of the extension tools registered when it
was built. The SDK fixes that list at session creation, so a tool an
extension registers at session_start or later (pi's dynamic-tools example)
was never callable, even with extensions loaded.

A child that loads extensions without `ext:` selectors now gets no
allowlist: `noTools: "builtin"`, its built-ins as `+name` entries, and the
other built-ins and the reserved subagent tools excluded, so an extension
still cannot switch on a built-in the profile leaves out. The snapshot
records this so reload and reopen behave the same. `ext:` selectors keep
the name allowlist: the SDK admits tools by name only and offers no
source-aware hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant