Skip to content

v1.108.316 — A display preference edited the data it was displaying

Latest

Choose a tag to compare

@jgravelle jgravelle released this 03 Sep 03:58

A display preference edited the data it was displaying

The shared tool-result cache stored the caller's dict and handed that same dict
back, so the MCP dispatcher's metadata step — meta_fields, a per-user display
setting — reached into the session cache and changed what every later caller was
served. Reported by @rknighton, twice: #570 for the crash and #572 for the cause,
with a standard-library reproduction that builds its own repo and needs no
fixtures.

meta_fields: [] is the shipped default, so out of the box the second
find_references or get_blast_radius call came back KeyError: '_meta'.

The crash was the loud case. suppress_meta is a per-call argument, so on an
ordinary config one call passing it emptied the shared entry and the next caller
— who had asked for metadata — was served an empty _meta. A partial
meta_fields does the same by replacement.

The window is the miss path. Both cached tools rebuild _meta from
dict(cached) on a hit, so a repeat call survives; it is the call that fills
the cache that hands the dispatcher the stored object. That is why a two-call
reproduction shows the crash and neither quiet case, and why reproducing the
report's own second claim needed the suppressing call moved to position one.

Fixed in the cache, not at the two call sites, which was the reporter's
argument.
search_symbols keeps its own cache and had already paid for this
twice — #377 item 3 for _meta.verdict, then #404, also theirs, for the rows —
and neither fix reached the shared one. A third per-consumer patch clears both
tools today and arms the trap for the tool written next.

_isolate clones containers only, at unbounded depth. Leaves in a tool result
are JSON-serialisable immutables by the time they reach the cache, and
container-only measured 4.15 ms against copy.deepcopy's 16.58 ms on an 800 KB
response
. Seven is assertions in tests/test_result_cache.py are == now:
identity was the defect written down, not a contract anyone wanted.

Also in this release

  • search_symbols(kind="field") was refused by both gates (#571,
    @devtomnl). field was a valid extracted kind that neither the JSON-schema
    enum nor the validator accepted. KIND_ORDER is the single authority now and
    the published enum is derived from it.
  • The receipt's dollar figure states what it prices. savings_usd_basis /
    savings_usd_note on receipt --export json and --rates, plus a line on
    both human surfaces. Each avoided token is priced once at the uncached list
    input rate — a floor, not an estimate. The arithmetic is unchanged.
  • get_ranked_context's description trimmed to recover core-tier schema
    headroom, and the stale holdout_results.json route-recall artifact regated.

Suite 9,161 passed / 13 skipped / 0 failed; CI green on all nine jobs; the
CI-environment reproduce on 3.13 ran 9,155 passed / 19 skipped.