Expose the resolved tool registry so a configuration can detect when a tool's default availability changes
The problem
When a tool's default availability changes, there is no signal a configuration
can key on. The change is announced in the changelog and takes effect silently in
every session; anything built on that tool keeps asserting it exists.
v2.1.233 is the worked example. The entry reads:
Todo/task-tracking tools (TaskCreate/Get/Update/List, TodoWrite) are no longer available on Opus 4.8, Sonnet 5, Fable 5, Mythos 5, and newer models
That is a clear changelog entry, and it is not the issue here. The issue is what
happens to configuration that was written against those tools. In my case a
documented protocol mandates a task-tracker at the entry of every phased
operation, restated across several hundred command and skill files, plus a
completion gate whose first step is "call TaskList and verify every entry is
completed". Overnight, that mandate became unrunnable everywhere at once and the
gate's first step became unexecutable — while every one of those files went on
asserting it. Nothing in-session said so. I found it days later by correlatingdeferred_tools_delta records across ~3,700 local transcripts against the version
field.
This is not a complaint that the tools were disabled — #80487 covered that and is
closed, and CLAUDE_CODE_ENABLE_TODO_TOOLS=1 restores them. It is that the
availability change itself was undetectable from inside a session, so the
failure was silent rather than loud, and it will be silent again the next time any
tool's default moves.
Working out why the tools were missing is a good illustration of the gap. On
v2.1.240 availability resolves from four independent inputs ANDed and ORed
together: CLAUDE_CODE_ENABLE_TASKS (a kill switch — only an explicit false
disables, and it beats everything else), a model gate covering opus ≥ 4.8,
sonnet ≥ 5, fable ≥ 5, mythos ≥ 5, CLAUDE_CODE_ENABLE_TODO_TOOLS as the
override for that gate specifically, and a remote-config flag. Each is
reasonable on its own. A fifth gate sits on top at a different layer: an agent or
command tools: / allowed-tools allowlist can refuse the call even when the
registry resolves it as present. None of the five is introspectable, so
establishing which one applied to a given session meant reading the availability
function out of the binary and then running a matrix of probes to confirm it.
Reading the open issues on this topic afterwards, the reports look less
contradictory than they first appear — different reporters are hitting different
branches of that same tree (a model outside the gate, the kill switch, the remote
flag, an allowlist). I do not think anyone was wrong; there was just no way for
any of us to see which branch we were on.
What I had to build instead
A SessionStart hook that reads the newest transcripts under ~/.claude/projects,
finds the deferred_tools_delta attachment, and checks whether TaskCreate
appears in addedNames. It works, but it is inference from a private on-disk
format I do not own, and it comes with the problems you would expect: it cannot
see the current session's registry (that record is not written until at or after
the first user turn, which is after a SessionStart hook runs), so it reports one
session late; and it needs a consensus rule across several transcripts because
sessions with and without the tools interleave on one machine.
None of that should be necessary to answer "is tool X available right now?", and
the hook has a second problem the first one hides: to avoid false alarms it has to
re-implement the vendor's own gate logic. A session on an ungated model hasTaskCreate for reasons unrelated to any opt-in, so counting it as evidence
silences the check — which means the hook now carries a copy of the model list and
will quietly drift out of agreement with it.
The ask
Expose the resolved tool registry to configuration. Any one of these would be
enough, roughly in order of preference:
- Include the resolved tool list in the JSON already passed to hooks on stdin.
A SessionStart hook could then assert its own preconditions directly.
- A CLI form —
claude tools list --json— that reports what the current model
and settings resolve to.
- Failing both, a startup diagnostic (or a
claude doctorcheck) that warns when
a tool named in a project's configuration is not in the registry.
(1) seems smallest: hooks already receive structured input, and this is data the
process has resolved by the time the hook fires.
Environment
- Claude Code v2.1.240, macOS (Darwin 25.5.0), Opus 5
- Related: #80487 (closed), #80566, #80305