fix(agent-core): re-register builtin tools when provider resolves late - #1688
Conversation
When the model provider becomes resolvable only after the agent starts (async OAuth / managed free-tokens registration), the builtin tool table stayed empty: initializeBuiltinTools was gated on hasProvider at fixed checkpoints and none fired. loopTools now self-heals on read once a provider resolves, so tool calls no longer fail with "Tool not found".
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b559cf97ff
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (this.builtinTools.size === 0 && this.agent.config.hasProvider) { | ||
| try { | ||
| this.initializeBuiltinTools(); |
There was a problem hiding this comment.
Ensure shell commands also trigger lazy tool init
In late-provider sessions where the user runs a ! shell command before any model step, this lazy path is never reached: runShellCommand() still reads this.builtinTools.get('Bash') directly and returns “Bash tool is not available.” With this fix, prompting the model first heals the table, but the documented shell-command entry point remains broken until a prompt happens; factor this into a shared ensure step or call it from runShellCommand before the direct lookup.
Useful? React with 👍 / 👎.
Related Issue
N/A — no linked issue; the problem is explained below.
Problem
When the model provider becomes resolvable only after the session/agent has started — e.g. OAuth login or managed free-tokens model registration completing asynchronously — the agent runs with zero builtin tools. The system prompt still advertises the standard tools (Read/Bash/Glob/...), so the model emits tool calls, but every call fails with
Tool "X" not found. The agent cannot read files, run commands, or use any builtin tool until the session is restarted/resumed (which rebuilds the table in a fresh process).What changed
Root cause.
initializeBuiltinTools()populates the executable tool table, but it is gated onhasProviderat every call site (ToolManager constructor, config update, skills-loaded, kaos-set). A provider that becomes resolvable asynchronously trips none of those checkpoints, so the table stays empty for the whole process — while the system prompt (which is independent of the table) keeps advertising the tools.Fix.
loopTools— which is re-read before every step — now self-heals: when the builtin table is empty but a provider is resolvable, it runsinitializeBuiltinTools()on the spot, so the table is populated on the first step after the provider resolves. Steady state short-circuits on the empty-table check, sohasProvideris not re-evaluated per read (no per-step cost); a failure is logged and retried on the next read.Poll-on-read is deliberate: the async registration path mutates the config the provider resolver reads without emitting any agent-level config event, so an event-driven re-init hook would not fire.
Tests. Added a regression test that builds an agent whose provider is initially unresolvable (empty live config), enables tools (table stays empty), then registers the provider asynchronously and asserts
loopToolsself-heals and the tools become dispatchable. Verified it fails without the fix and passes with it; the surrounding tool/turn/loop/mcp suites (230 tests) andtsc --noEmitare green.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.