You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MCP stdio get_record answers a missing or hidden id with uncoded error text, while the HTTP door answers RECORD_NOT_FOUND 404 #33030
Start the server with stdio MCP: os start with OS_MCP_STDIO_ENABLED=true and OS_MCP_STDIO_API_KEY=an admin osk_ key.
Over stdio, tools/call get_record {objectName:"showcase_task", recordId:"qa_missing_id_123"}.
Actual: result.isError true and content[0].text = Record "qa_missing_id_123" not found in "showcase_task". There is no {error:{code,status}}.
An RLS-hidden id under a member key answers the same way.
The same call on the HTTP door (/api/v1/mcp) answers {error:{code:"RECORD_NOT_FOUND", status:404, message}}. Stdio update_record on the same missing id also answers the coded RECORD_NOT_FOUND.
Expected: the coded envelope on both doors, which is the #23509 contract: a stdio client branches on the code.
Mechanism
createStdioDataBridge.get returns null by design (packages/mcp/src/stdio-data-bridge.ts ~1209: "get_record owns the not-found wording on this path"). get_record's record == null branch then renders errorResult(\Record "…" not found in "…"`) (packages/mcp/src/mcp-http-tools.ts~2633) instead oferrorResultWithCode`.
Impact: low. This is a residual of the closed #23509.
QA-source: #33025 · ai.mcp-coded-refusals-and-runtime-exposure · observed beside the clauses
Line: a host developer on the stdio MCP transport · get_record and update_record on a missing or hidden id · every such call · measured twice.
Grade: the stdio door answers uncoded error text where the HTTP door answers RECORD_NOT_FOUND 404. It is still a loud refusal, not a wrong answer, so the MCP focus raise does not apply (rule 8). A user-visible inconsistency is p2; one step down for the non-default stdio transport (host developer) gives p3. It is linked under the MCP epic [Epic] MCP: what an agent can read and write on the HTTP MCP door, and answers that agree with the gate #32897.
Claim: PM loop round 1 · epic seat epic:#32897#1 (registration 6867120489) · 2026-11-13T23:13Z
Session: session_01L9qibvhQvy8SbR3j9f7kcb (card thread; seat session_01L9qibzoApSyVPuia3NCrBY)
Account: os-tesla
Branch: claude/project-thread-3d24w1 (the card thread's harness-pinned branch; the thread can push only this name)
Worktree: objectstack-issue-33030
Domain: domain:server
Seat: epic:#32897#1
File surface: as the claim on #33156 (one PR for both cards): packages/mcp/src/stdio-data-bridge.ts, packages/mcp/src/mcp-http-tools.ts (the get_record branch only), their tests, root .changeset/ (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: platform code in objectstack (get_record's null branch in packages/mcp/src/mcp-http-tools.ts ~2633 renders uncoded errorResult); the platform path that already covers it is errorResultWithCode RECORD_NOT_FOUND, as stdio update_record and the HTTP door answer; reached by a stdio client that branches on the code — measured twice by qa#3
Thread-read: 6845307900
Premise: holds (read at main c04aa295; the thread re-measures first)
Serial constraints cleared: PR #33150 (#32530) touches mcp-http-tools.ts; merge queue serialises, merge main after it lands
Order: /mnt/project-files/epic-32897-1/r2026-11-13/dispatch-33156-33030.md under that folder's FRAME.md. Contract review: this seat, at CONTRACT_REVIEW_TIER.
{
"issue": [
33156,
33030
],
"status": "done",
"branch": "claude/project-thread-3d24w1",
"pr": "https://github.com/objectstack-ai/objectstack/pull/33175",
"head": "59ccee50",
"session": "session_01L9qibvhQvy8SbR3j9f7kcb",
"premise_still_valid": true,
"summary": "#33030: stdio get_record's null branch now throws recordNotFoundError into its own catch, so the stdio door renders the HTTP door's coded RECORD_NOT_FOUND / 404 (dataDoorErrorResult) for a missing id and a row-filter-hidden id alike; update_record / delete_record were already coded and are pinned. #33156 (after PR #33150 merged, 28a2f0f5 merged once): callerObjectView registers a package-internal declared read (setDeclaredObjectRead, keyed weakly by the view) answering the served object and its declaration from one read; describeServedObject(def, declared) judges readonly on the declared field like the HTTP twin, and builds the answer from the served object only. Both stdio describe_object and the objects resource read through readObjectWithDeclaration; plugin.ts unchanged.",
"tests": "Measured first: #33030 pins at 47f6c939 3 failed | 5 passed (stdio text 'Record \"ghost\" not found in \"task\"', uncoded); #33156 pins at 28a2f0f5 2 failed | 32 passed (marked_up readonly false on tool and resource). At head: not-found 8/8, object-schema-mask 34/34, runtime mcp-object-schema-mask 73/73; pnpm --filter @objectstack/mcp test 64 files / 1148 tests passed (at c1b63360; 59ccee50 only reshapes a test double); mcp + runtime typecheck exit 0. Ablations via ablation-replace.mjs, each restored blob == HEAD: get_record bare sentence -> 3 red; served-field readonly -> 2 red; setDeclaredObjectRead removed -> 2 red; describe from the declaration -> 9 red (expression/hidden_cost not-served pin and case-table masking pins). Source-imported suites, no dist leg.",
"gates": "dispatch-gates --commands at 59ccee50: 66 families, each with stdin closed, exit captured before any pipe; --ran: 66 accounted, 65 run, 1 NOT-MEASURED (check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET, needs repo-wide dist; CI runs it). plugin-teardown-shape --self-test exit 3 in the shallow clone, 0 after fetching 621a4876. engine-double-contract exit 1 on a second double, fixed by reusing the file's one double, exit 0. Test-reading gates re-run at 59ccee50, exit 0. check:type-check-debt 0, pnpm lint 0. PR check-runs: in_progress at report time.",
"line_budget": "8 files, +424 -25 (two changesets, 3 mcp sources, 3 test files)",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"deviations": [
"get_record null branch throws recordNotFoundError into the catch (rendered by dataDoorErrorResult, the HTTP throw's renderer) instead of calling errorResultWithCode directly as the order words it; same bytes today, stays equal if the /data door adds a member to this code.",
"#33156 parity pin across doors is one literal pinned on each side (mcp stdio tests + a runtime PARITY case on the real-layer harness): neither package depends on the other.",
"Runtime full test suite not run locally: its only change is one test file (73/73); declared to CI.",
"Report posted on both cards (one PR closes both)."
],
"files_changed": [
".changeset/33030-stdio-get-record-coded-not-found.md",
".changeset/33156-stdio-describe-readonly-declared.md",
"packages/mcp/src/mcp-http-tools.ts",
"packages/mcp/src/mcp-server-runtime.ts",
"packages/mcp/src/stdio-data-bridge.ts",
"packages/mcp/src/stdio-data-bridge.not-found.test.ts",
"packages/mcp/src/stdio-data-bridge.object-schema-mask.test.ts",
"packages/runtime/src/domains/mcp-object-schema-mask.test.ts"
],
"mcp_calls": "0",
"api_writes": "2 relay writes so far — pr_create (fleet-write run 43531716436, PR #33175) and this os-dev-report comment (per card); 0 direct REST writes",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: b · reach: stdio resources/read objectstack://objects/task/records/ghost over the long-lived server at 59ccee50 answers {\"error\":\"Record \\\"ghost\\\" not found in \\\"task\\\"\"} with no code/status, while the two sibling object resources answer { error, code, status } and the same handler's catch adds code/status for a thrown coded refusal (mcp-server-runtime.ts bridgeResources record_by_id) · dedupe: record_by_id resource not found code, objectstack records resource RECORD_NOT_FOUND, stdio record resource uncoded miss",
"carrier: seat · noted, not filed: runtime/src/domains/mcp.ts writeNeverSetsField JSDoc last sentence ('only the masked object to ask') and the unreleased .changeset/32530 sentence on the stdio expression case are now stale; outside this PR's surface"
]
}
Landed · #33030 · 2026-11-14T01:14Z — PR #33175 merged as f45c0125 (one PR for #33156 and #33030); Fixes #33030 closed this card. pm:dispatched off in this stroke.
Delivered: stdio get_record answers a missing or hidden id with the coded RECORD_NOT_FOUND / 404, and stdio describe_object and the objects resource judge readonly on the declared field, as the HTTP door does; the hidden expression is still not served. The pending note .changeset/32530 was corrected in the same PR (seat record 6868774819). Left: the stdio record resource's uncoded miss is #33178.
Activity
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: first touch →
domain:server· p3 ·pm:queue· 2026-11-13T05:47Z⛔ Not a claim.
get_recordandupdate_recordon a missing or hidden id · every such call · measured twice.RECORD_NOT_FOUND404. It is still a loud refusal, not a wrong answer, so the MCP focus raise does not apply (rule 8). A user-visible inconsistency is p2; one step down for the non-default stdio transport (host developer) gives p3. It is linked under the MCP epic [Epic] MCP: what an agent can read and write on the HTTP MCP door, and answers that agree with the gate #32897.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
epic:#32897#1 · os-tesla · session_01L9qibzoApSyVPuia3NCrBY
Claim: PM loop round 1 · epic seat
epic:#32897#1(registration 6867120489) · 2026-11-13T23:13ZSession:
session_01L9qibvhQvy8SbR3j9f7kcb(card thread; seatsession_01L9qibzoApSyVPuia3NCrBY)Account:
os-teslaBranch:
claude/project-thread-3d24w1(the card thread's harness-pinned branch; the thread can push only this name)Worktree:
objectstack-issue-33030Domain:
domain:serverSeat:
epic:#32897#1File surface: as the claim on #33156 (one PR for both cards):
packages/mcp/src/stdio-data-bridge.ts,packages/mcp/src/mcp-http-tools.ts(theget_recordbranch only), their tests, root.changeset/(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: platform code in objectstack (
get_record's null branch inpackages/mcp/src/mcp-http-tools.ts~2633 renders uncodederrorResult); the platform path that already covers it iserrorResultWithCodeRECORD_NOT_FOUND, as stdioupdate_recordand the HTTP door answer; reached by a stdio client that branches on the code — measured twice by qa#3Thread-read: 6845307900
Premise: holds (read at main
c04aa295; the thread re-measures first)Serial constraints cleared: PR #33150 (#32530) touches
mcp-http-tools.ts; merge queue serialises, merge main after it landsOrder:
/mnt/project-files/epic-32897-1/r2026-11-13/dispatch-33156-33030.mdunder that folder'sFRAME.md. Contract review: this seat, atCONTRACT_REVIEW_TIER.Generated by Claude Code
objectstack-fleet commentedon Nov 14, 2026
epic:#32897#1 · os-tesla · session_01L9qibvhQvy8SbR3j9f7kcb
os-dev-report
{ "issue": [ 33156, 33030 ], "status": "done", "branch": "claude/project-thread-3d24w1", "pr": "https://github.com/objectstack-ai/objectstack/pull/33175", "head": "59ccee50", "session": "session_01L9qibvhQvy8SbR3j9f7kcb", "premise_still_valid": true, "summary": "#33030: stdio get_record's null branch now throws recordNotFoundError into its own catch, so the stdio door renders the HTTP door's coded RECORD_NOT_FOUND / 404 (dataDoorErrorResult) for a missing id and a row-filter-hidden id alike; update_record / delete_record were already coded and are pinned. #33156 (after PR #33150 merged, 28a2f0f5 merged once): callerObjectView registers a package-internal declared read (setDeclaredObjectRead, keyed weakly by the view) answering the served object and its declaration from one read; describeServedObject(def, declared) judges readonly on the declared field like the HTTP twin, and builds the answer from the served object only. Both stdio describe_object and the objects resource read through readObjectWithDeclaration; plugin.ts unchanged.", "tests": "Measured first: #33030 pins at 47f6c939 3 failed | 5 passed (stdio text 'Record \"ghost\" not found in \"task\"', uncoded); #33156 pins at 28a2f0f5 2 failed | 32 passed (marked_up readonly false on tool and resource). At head: not-found 8/8, object-schema-mask 34/34, runtime mcp-object-schema-mask 73/73; pnpm --filter @objectstack/mcp test 64 files / 1148 tests passed (at c1b63360; 59ccee50 only reshapes a test double); mcp + runtime typecheck exit 0. Ablations via ablation-replace.mjs, each restored blob == HEAD: get_record bare sentence -> 3 red; served-field readonly -> 2 red; setDeclaredObjectRead removed -> 2 red; describe from the declaration -> 9 red (expression/hidden_cost not-served pin and case-table masking pins). Source-imported suites, no dist leg.", "gates": "dispatch-gates --commands at 59ccee50: 66 families, each with stdin closed, exit captured before any pipe; --ran: 66 accounted, 65 run, 1 NOT-MEASURED (check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET, needs repo-wide dist; CI runs it). plugin-teardown-shape --self-test exit 3 in the shallow clone, 0 after fetching 621a4876. engine-double-contract exit 1 on a second double, fixed by reusing the file's one double, exit 0. Test-reading gates re-run at 59ccee50, exit 0. check:type-check-debt 0, pnpm lint 0. PR check-runs: in_progress at report time.", "line_budget": "8 files, +424 -25 (two changesets, 3 mcp sources, 3 test files)", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "deviations": [ "get_record null branch throws recordNotFoundError into the catch (rendered by dataDoorErrorResult, the HTTP throw's renderer) instead of calling errorResultWithCode directly as the order words it; same bytes today, stays equal if the /data door adds a member to this code.", "#33156 parity pin across doors is one literal pinned on each side (mcp stdio tests + a runtime PARITY case on the real-layer harness): neither package depends on the other.", "Runtime full test suite not run locally: its only change is one test file (73/73); declared to CI.", "Report posted on both cards (one PR closes both)." ], "files_changed": [ ".changeset/33030-stdio-get-record-coded-not-found.md", ".changeset/33156-stdio-describe-readonly-declared.md", "packages/mcp/src/mcp-http-tools.ts", "packages/mcp/src/mcp-server-runtime.ts", "packages/mcp/src/stdio-data-bridge.ts", "packages/mcp/src/stdio-data-bridge.not-found.test.ts", "packages/mcp/src/stdio-data-bridge.object-schema-mask.test.ts", "packages/runtime/src/domains/mcp-object-schema-mask.test.ts" ], "mcp_calls": "0", "api_writes": "2 relay writes so far — pr_create (fleet-write run 43531716436, PR #33175) and this os-dev-report comment (per card); 0 direct REST writes", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: b · reach: stdio resources/read objectstack://objects/task/records/ghost over the long-lived server at 59ccee50 answers {\"error\":\"Record \\\"ghost\\\" not found in \\\"task\\\"\"} with no code/status, while the two sibling object resources answer { error, code, status } and the same handler's catch adds code/status for a thrown coded refusal (mcp-server-runtime.ts bridgeResources record_by_id) · dedupe: record_by_id resource not found code, objectstack records resource RECORD_NOT_FOUND, stdio record resource uncoded miss", "carrier: seat · noted, not filed: runtime/src/domains/mcp.ts writeNeverSetsField JSDoc last sentence ('only the masked object to ask') and the unreleased .changeset/32530 sentence on the stdio expression case are now stale; outside this PR's surface" ] }objectstack-fleet commentedon Nov 14, 2026
epic:#32897#1 · os-tesla · session_01L9qibzoApSyVPuia3NCrBY
Landed · #33030 · 2026-11-14T01:14Z — PR #33175 merged as
f45c0125(one PR for #33156 and #33030);Fixes #33030closed this card.pm:dispatchedoff in this stroke.Delivered: stdio
get_recordanswers a missing or hidden id with the codedRECORD_NOT_FOUND/ 404, and stdiodescribe_objectand the objects resource judgereadonlyon the declared field, as the HTTP door does; the hidden expression is still not served. The pending note.changeset/32530was corrected in the same PR (seat record 6868774819). Left: the stdio record resource's uncoded miss is #33178.Generated by Claude Code