Uh oh!
There was an error while loading. Please reload this page.
fix(mcp): stdio bridge throws the shared RECORD_NOT_FOUND envelope - #8507
fix(mcp): stdio bridge throws the shared RECORD_NOT_FOUND envelope#8507os-zhuang wants to merge 3 commits into
Conversation
The stdio MCP bridge's update()/remove() by-id write seams minted their own bare Error on a missing id. The HTTP bridge's callData path already throws recordNotFoundError (code RECORD_NOT_FOUND, status 404) for the identical miss, so the two transports answered the same operation with two different envelopes. Also tightens check-engine-double-contract.mjs's consumer-seam invariant from "refuses at all" to SHARED_ONLY, now that all four seams reach the shared envelope.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P7vaLs7bhBPi9m3JyzkhDj
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
The third test's `.catch((e) => e)` idiom inferred the settled value as `unknown` (TResult from an `any`-typed catch parameter widens to `unknown` here), which is a second, unrelated way to fail tsc from the same file that already had this idiom's cousin (`await res.json()`) in the package's 53-error TEST_DEBT baseline. Replaced it with the same explicit-cast helper the other two tests already used, extracted once as `catchError`. pnpm check:type-check-debt (the real ratchet command, not check:type-check-coverage) on a full built closure: OK, 33 ledger entries re-measured, none above their recorded number — @objectstack/mcp back to its recorded 53. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P7vaLs7bhBPi9m3JyzkhDj
os-zhuang
commented
Aug 13, 2026
CI follow-up on the two red checks flagged. TypeScript Type Check ( Verified with the actual command, not the one I ran the first time: ESLint — confirmed a flake, not mine. Polled the new SHA's check-runs after push: ESLint completed Pushed as Generated by Claude Code Generated by Claude Code |
Fixes#8422
What changed
packages/mcp/src/stdio-data-bridge.tsminted its own localrecordNotFound(object, id)— a bareErrorwith neithercodenorstatus— thrown from theupdate()andremove()by-id write seams on a missing id. The HTTP bridge'scallDatapath throws the repo's ONE not-found envelope,recordNotFoundError(code: 'RECORD_NOT_FOUND',status: 404,packages/core/src/utils/record-not-found.ts, #4435/#5138/#7867), for the identical miss. Same operation, two transports, two envelopes.The local mint is deleted; both seams now
throw recordNotFoundError(object, id).Line numbers were stale (#8439 / #8266 touched this file since the card was filed) — located both call sites by symbol (
recordNotFound(object, id)calls insideupdate()/remove()), not by the card's cited:337/:347.Import path:
import { recordNotFoundError } from '@objectstack/core', not@objectstack/metadata-protocol's re-export.@objectstack/mcp'spackage.jsonalready declares a direct@objectstack/coredependency, andpackages/mcp/src/plugin.tsalready imports named exports from it —@objectstack/coreis also the lower of the two packages that carry the factory (packages/objectql/src/engine.ts's own #7867 comment explains why:@objectstack/objectqlcannot import@objectstack/metadata-protocolat all, ADR-0076 D2). No new dependency needed either way.Preserved, not touched: the local factory's comment explaining why it throws rather than returns (
registerObjectToolsturns a throw into a tool error) — that reasoning is correct and now sits at theupdate()call site (the first of the two seams), withremove()pointing back at it. Only which error object is thrown changed.Adjacent deliverable — folded in, not filed as a follow-up
scripts/check-engine-double-contract.mjs's consumer-seam invariant (#8194) reported the two stdio seams asrefusal: 'local'rather than reddening, because the gate's own header names this as a deliberate, temporary state pending this exact card, with a notedSHARED_ONLY-shaped one-line tightening. Since the seam list was already both-directions complete and the tightening really is mechanical, I folded it into this PR rather than filing a follow-up:scripts/check-engine-double-contract.mjs— the seam filter changed fromseams.filter((x) => !x.refusal)(only "no refusal at all" failed) toseams.filter((x) => x.refusal !== 'shared')(a local mint now fails too), plus an updated header comment (## WHICH not-found envelope (#8194, tightened to SHARED_ONLY by #8422)) and an adjustedREFUSESmessage that distinguishes "refuses through a locally minted error" from "does not refuse anywhere before it".--self-test) still green — none of its synthetic fixtures assert the pre-tightening filter behavior, only therefusalclassification (shared/local/null), which is unchanged.[shared](was 2[shared]+ 2[local]); gate is green.Reverse verification
Prediction (recorded before running): reverting only
stdio-data-bridge.tsto its pre-fix state, while keeping the tightened gate and the new test file, goes red in both places — the new unit tests (assertingcode/status) fail, andcheck-engine-double-contract.mjsreddens because the two stdio seams revert torefusal: 'local', which the tightenedSHARED_ONLYfilter now rejects. Ordinary direction (more red), no inversion expected.Measured, via
git checkout HEAD~1 -- packages/mcp/src/stdio-data-bridge.ts(fix already committed, so this is a real restore point) against the tightened gate + new tests, then restored viagit checkout claude/issue-8422-stdio-shared-not-found-envelope -- packages/mcp/src/stdio-data-bridge.ts:expected undefined to be 'RECORD_NOT_FOUND'.REFUSESerrors —stdio-data-bridge.ts:351and:361, both "refuses through a locally minted error rather than the shared envelope".Matches the prediction. Restored cleanly (
git diff HEAD -- packages/mcp/src/stdio-data-bridge.tsempty); both re-verified green afterward.Tests
packages/mcp/src/stdio-data-bridge.not-found.test.ts— covers both by-id write seams (update()andremove()) against a missing id, assertingerr.code === 'RECORD_NOT_FOUND'anderr.status === 404specifically (not just that something threw), plus a same-shape-on-both-seams check.pnpm --filter @objectstack/mcp test→ 16 test files, 173 tests passed.pnpm --filter @objectstack/mcp typecheck→ clean.pnpm --filter @objectstack/mcp build→ clean (also built the dependency closure first:pnpm --filter '@objectstack/mcp^...' build).node scripts/check-engine-double-contract.mjs --self-test && node scripts/check-engine-double-contract.mjs→ self-test OK, real run OK (197 pinned, 133 DEBT, 2 exempt; all 4 consumer seams[shared]).node scripts/check-error-code-casing.mjs→ OK.node scripts/check-nul-bytes.mjs→ OK.node scripts/check-cross-package-test-inputs.mjs→ OK (new test file's package-scoped read is already covered).pnpm run check:query-options-erasure→ OK (test surface unaffected — the new file has no query-options sites).pnpm run check:type-check-coverage→ OK, ledger unchanged (13 packages / 436 frozen errors in DEBT, unchanged; new test file typechecks cleanly, no debt raised).pnpm exec eslinton the three changed files → clean.node scripts/pm/dispatch-gates.mjs <changed paths>→ surfacedcheck:cross-package-test-inputs,check:engine-double-contract(both already run above) and, as convention-triggered by adding a test file,check:query-options-erasure+check:type-check-coverage(both run above, both green — no addition beyond the prompt's named families was otherwise needed).Changeset
.changeset/mcp-stdio-record-not-found-envelope.md—@objectstack/mcp,patch(behavior fix, no exported symbol or authorable metadata moves).Not in scope
Every other divergence between the two MCP transports noted in the file's own docblock (
callData's protocol-service preference, ingressreadonlystrip, existence probes,expand/select) — deliberate, filed, and untouched here. Also not addressed: #8328 (mcp-server-runtime.ts,needs-user-decision) — out of this card's file surface.Refs #8194, #8083, #8266, #7867, #5138, #4435.
Generated by Claude Code