Skip to content

v10.5.1 — Classified

Choose a tag to compare

@miisodev miisodev released this 16 Aug 17:31
· 7 commits to main since this release

Three tools shipped unclassified. assembly, claim and diff arrived across v10.3 and v10.4 and none of them was ever added to the tool-annotation table, so every client has been showing all three in the wrong permission group since the day they landed.

Why it survived three releases

The mechanism worked exactly as designed. An unclassified tool falls back to write — never read — and applyToolAnnotations() returns it in unclassified, which the server prints to stderr on every boot.

So the warning fired. Correctly, on every single start, for three releases. The server's stderr is a Railway container log nobody tails.

A safe default plus a notification nobody reads is indistinguishable from correctness right up until someone checks.

The harm is also the opposite of the obvious one. No write leaked into the read-only group — the fail-safe prevents that. It was two genuine reads stuck in the write bucket, so assembly and diff prompted on every call. That is precisely how a user learns to answer "always allow" once and stop reading prompts, which is the same failure as a maintenance flag that always fires.

The classification

assembly and diff are reads. Neither writes anything.

claim is append, not write. It is mode-inferred and its modes disagree: listing and reading write nothing, registering is idempotent (deduped by assertion), and recording a verification appends a dated line every time. A tool gets one annotation, so it takes the weakest guarantee any of its modes can honour — a second verify call really is a second effect. Classifying it by its cheapest mode would be the exact mistake the table exists to prevent.

The full surface, now that it is actually split:

read write append destructive
Core (42) 23 13 4 2
Raw / full mode (33) 11 13 5 4

Core and raw are composed, not nested

registerTools() took a mode flag and reached into tools-advanced.ts itself, so "core" and "full" were one tangled surface with the boundary expressed as a boolean halfway down a 3,800-line file.

The caller composes them now: core is core, full is core plus the raw set. What a mode contains is visible where the decision is made, and the two sets are provably disjoint — the SDK throws on a duplicate name, which is how the new test found a bug in itself on the first run.

Tests that make this class impossible rather than unlikely

src/annotations.test.ts fails when any registered tool is unclassified in either mode, when the table names a tool that no longer exists, or when a tool claims to be both read-only and destructive.

It also pins the tool counts at 42 / 33 / 75, deliberately. Those numbers appear in the README, the BLUEPRINT and three pages of the landing site; nothing derives them, and they have gone stale on three separate releases. Adding a tool now fails a test — which is the reminder to update the places that state the count. A tripwire, not an assertion.


190 tests, tsc --noEmit clean, build clean. 42 core tools, 75 with full mode.