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
os start with the stdio MCP transport drops a host plugin console.log written after boot: it reaches neither stdout nor stderr #32942
Found by the 18.4.0 release acceptance (wave anchor #32908) at main @ 5fb79bc0. Full evidence chain: QA run #32940, F-2, and its R4 comment.
What happens
With the stdio MCP transport on, a host plugin's console.log written after boot reaches neither stdout nor stderr. The tool-call interceptor seam itself runs over stdio (its deny and its malformed-verdict fault both answer correctly), but nothing the host logs from it is visible, so host diagnostics are lost on every stdio deployment and the checklist's stdio log oracle can never be met.
Reproduction
Scratch copy of examples/app-showcase whose config adds a plugin that installs a tool-call interceptor which console.logs one [qa-interceptor] {door, transport, toolName, userId} line per call, denies delete_record and answers describe_object with a malformed verdict.
Mint an osk_ key. Run objectstack start --no-ui -p PORT -d file:DB with OS_MCP_STDIO_ENABLED=true and OS_MCP_STDIO_API_KEY=KEY.
Over stdin: initialize, notifications/initialized, then tools/call for list_objects, delete_record, describe_object. Capture both pipes in full.
Expected one [qa-interceptor] … "transport":"stdio" line per call on stderr. Actualdelete_record → PERMISSION_DENIED from the interceptor and describe_object → INTERNAL_ERROR with the MCP WARN on stderr, but no [qa-interceptor] line on either stream. Control: the same copy and DB under os start over HTTP print the line on stderr. 3 reproductions (SDK stdio client once, raw pipe twice).
Where to look
Not pinned. Candidates: the stdout→stderr redirect and the boot-quiet console swap / restoreOutput on the stdio path in packages/cli/src/commands/serve.ts, or the stdio transport's own stdout claim (packages/mcp/src/mcp-server-runtime.ts, protocol-stdout.ts).
Claim: PM loop round 10, card thread · 2026-11-13T21:23Z · MCP p2 under the maintainer's 「mcp 相关 p2 任务也可以派发处理」 (2026-11-13T15:50:52Z)
Session: session_01L9qibwitFuZejYP8o8ZdXC
Account: os-tesla
Branch: claude/project-thread-tfbq5b
Worktree: objectstack-issue-32942
Domain: domain:server
Seat: domain:server#7
File surface: packages/cli/src/commands/serve.ts (the stdio console / redirect seam), packages/mcp/src/protocol-stdout.ts and packages/mcp/src/mcp-server-runtime.ts (only the stdout claim), their tests, one root changeset. Stop on breach.
Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; PM default), effort: medium
Clause-②: no — host diagnostics written after boot reach stderr under the stdio transport; no answer changes
Metadata-protocol: none
Responsibility: the lost line comes from the stdio path's console handling in os start (packages/cli) or the stdio transport's stdout claim (packages/mcp), objectstack | the HTTP start path already prints the same line on stderr | every stdio MCP deployment's host diagnostics reach it today
Thread-read: 6840400886
Premise: re-measure — reproduced 3 times by devx#1 at 5fb79bc0; the dev reproduces at origin/main first
Serial constraints cleared: mcp-server-runtime.ts is shared with this seat's #33150 (#32530): edits stay on the stdout claim; no open seat-7 PR touches serve.ts or protocol-stdout.ts
{
"issue": 32942,
"status": "done",
"branch": "claude/project-thread-tfbq5b",
"pr": "https://github.com/objectstack-ai/objectstack/pull/33159",
"session": "session_01L9qibwitFuZejYP8o8ZdXC",
"premise_still_valid": true,
"summary": "Reproduced at origin/main b22be279 on both os serve and os start, but only with SDK-client timing: initialize at spawn, tools/call on its answer. The calls are served inside serve's boot-quiet window (the transport attaches inside runtime.start(), the window closes after it returns), which no-ops console.log/debug and filters process.stdout.write to kernel-logger records, so the interceptor line reached neither stream; stdout never carried a non-frame line. protocolStdout() now marks process.stdout with a Symbol.for key when the stdio transport claims it, and serve's window stops holding back console and stdout bytes from that moment (they go to the stderr forwarder, never stdout). HTTP-only boots never claim, so their window is unchanged. Cost: under stdio, boot chatter written after the claim now prints on stderr (23 [Registry] lines on the fixture).",
"tests": "head f4bbd648: mcp `pnpm --filter @objectstack/mcp test` Test Files 64 passed (64), Tests 1138 passed (1138); cli unit tier `vitest run --project unit` Test Files 416 passed (416), Tests 5934 passed (5934); `OS_TEST_TIERS=nightly vitest run test/serve-stdio-stdout-purity.e2e.test.ts` Tests 3 passed (3) (#7915 case, #32942 stdio case, HTTP CONTROL). Ablation A (serve.ts quietHolds ignores the claim, rebuilt, dist-preflight absent in 952 files): 1 failed 2 passed, stdio case 'the console.log line of the stdio call is missing from stderr', CONTROL green; restored blob==HEAD 5d4e3372, rebuilt, marker present. Ablation B (mark line deleted in protocol-stdout.ts): mcp unit 1 failed 2 passed; mcp rebuilt, preflight absent in 12 files; e2e 1 failed 2 passed (stdio case); restored blob==HEAD 5cc99b80, rebuilt, marker present in dist/index.js and dist/index.cjs.",
"gates": "dispatch-gates --commands at f4bbd648: 72 families; 70 exit 0; check:dual-build-cjs-loads exit 3 and check:i18n-coverage exit 3 (PREREQUISITE NOT MET, NOT MEASURED); check-plugin-teardown-shape --self-test exit 3 on the shallow clone, exit 0 (48 cases) after fetching its pinned fixture commit. --ran reconciliation: 72 derived, 70 run, 2 NOT-MEASURED, 0 UNRUN. pnpm lint exit 0; mcp and cli typecheck exit 0. CI check-runs: in_progress at report time (not waited on). cli integration tier declared to CI.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "4 — reads only: issue_read get + get_comments on #32942, ToolSearch load; 0 write tools",
"api_writes": "2 relay writes so far — pr_create (#33159, assignees os-tesla) via fleet-write run 43521440587; this os-dev-report comment via post-stamped. git push is not a REST write.",
"open_questions": [
{
"question": "The e2e file is now about 19 s local wall over four boots and carries no test-budget line, as before this PR. Add one?",
"options": [
"A leave as is (nightly e2e, unchanged convention for this file)",
"B add a test-budget line once a CI reading exists"
],
"recommendation": "B, by the seat after the first nightly reading, because the budget rule wants a measured CI figure"
}
],
"security_findings": "none",
"out_of_scope_findings": [
"class: none · noted only: an HTTP request served before serve's boot-quiet window closes would lose its console lines the same way; an HTTP client does not normally reach the server before its banner and the card's control prints the line · carrier: none"
]
}
Activity
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: first touch →
domain:server· p2 ·pm:queue· 2026-11-13T00:48Z⛔ Not a claim.
domain:server(the stdio MCP transport's console handling inos start).Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx
Claim: PM loop round 10, card thread · 2026-11-13T21:23Z · MCP p2 under the maintainer's 「mcp 相关 p2 任务也可以派发处理」 (2026-11-13T15:50:52Z)
Session:
session_01L9qibwitFuZejYP8o8ZdXCAccount:
os-teslaBranch:
claude/project-thread-tfbq5bWorktree:
objectstack-issue-32942Domain:
domain:serverSeat:
domain:server#7File surface:
packages/cli/src/commands/serve.ts(the stdio console / redirect seam),packages/mcp/src/protocol-stdout.tsandpackages/mcp/src/mcp-server-runtime.ts(only the stdout claim), their tests, one root changeset. Stop on breach.Container & model:
M,mode:thread,model: default (dispatch-gates --tier: no path-derived mandate; PM default),effort: mediumClause-②: no — host diagnostics written after boot reach stderr under the stdio transport; no answer changes
Metadata-protocol: none
Responsibility: the lost line comes from the stdio path's console handling in
os start(packages/cli) or the stdio transport's stdout claim (packages/mcp), objectstack | the HTTP start path already prints the same line on stderr | every stdio MCP deployment's host diagnostics reach it todayThread-read: 6840400886
Premise: re-measure — reproduced 3 times by devx#1 at
5fb79bc0; the dev reproduces atorigin/mainfirstSerial constraints cleared:
mcp-server-runtime.tsis shared with this seat's #33150 (#32530): edits stay on the stdout claim; no open seat-7 PR touchesserve.tsorprotocol-stdout.tsobjectstack-fleet commentedon Nov 13, 2026
domain:server#7 · os-tesla · session_01L9qibwitFuZejYP8o8ZdXC
os-dev-report
{ "issue": 32942, "status": "done", "branch": "claude/project-thread-tfbq5b", "pr": "https://github.com/objectstack-ai/objectstack/pull/33159", "session": "session_01L9qibwitFuZejYP8o8ZdXC", "premise_still_valid": true, "summary": "Reproduced at origin/main b22be279 on both os serve and os start, but only with SDK-client timing: initialize at spawn, tools/call on its answer. The calls are served inside serve's boot-quiet window (the transport attaches inside runtime.start(), the window closes after it returns), which no-ops console.log/debug and filters process.stdout.write to kernel-logger records, so the interceptor line reached neither stream; stdout never carried a non-frame line. protocolStdout() now marks process.stdout with a Symbol.for key when the stdio transport claims it, and serve's window stops holding back console and stdout bytes from that moment (they go to the stderr forwarder, never stdout). HTTP-only boots never claim, so their window is unchanged. Cost: under stdio, boot chatter written after the claim now prints on stderr (23 [Registry] lines on the fixture).", "tests": "head f4bbd648: mcp `pnpm --filter @objectstack/mcp test` Test Files 64 passed (64), Tests 1138 passed (1138); cli unit tier `vitest run --project unit` Test Files 416 passed (416), Tests 5934 passed (5934); `OS_TEST_TIERS=nightly vitest run test/serve-stdio-stdout-purity.e2e.test.ts` Tests 3 passed (3) (#7915 case, #32942 stdio case, HTTP CONTROL). Ablation A (serve.ts quietHolds ignores the claim, rebuilt, dist-preflight absent in 952 files): 1 failed 2 passed, stdio case 'the console.log line of the stdio call is missing from stderr', CONTROL green; restored blob==HEAD 5d4e3372, rebuilt, marker present. Ablation B (mark line deleted in protocol-stdout.ts): mcp unit 1 failed 2 passed; mcp rebuilt, preflight absent in 12 files; e2e 1 failed 2 passed (stdio case); restored blob==HEAD 5cc99b80, rebuilt, marker present in dist/index.js and dist/index.cjs.", "gates": "dispatch-gates --commands at f4bbd648: 72 families; 70 exit 0; check:dual-build-cjs-loads exit 3 and check:i18n-coverage exit 3 (PREREQUISITE NOT MET, NOT MEASURED); check-plugin-teardown-shape --self-test exit 3 on the shallow clone, exit 0 (48 cases) after fetching its pinned fixture commit. --ran reconciliation: 72 derived, 70 run, 2 NOT-MEASURED, 0 UNRUN. pnpm lint exit 0; mcp and cli typecheck exit 0. CI check-runs: in_progress at report time (not waited on). cli integration tier declared to CI.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "4 — reads only: issue_read get + get_comments on #32942, ToolSearch load; 0 write tools", "api_writes": "2 relay writes so far — pr_create (#33159, assignees os-tesla) via fleet-write run 43521440587; this os-dev-report comment via post-stamped. git push is not a REST write.", "open_questions": [ { "question": "The e2e file is now about 19 s local wall over four boots and carries no test-budget line, as before this PR. Add one?", "options": [ "A leave as is (nightly e2e, unchanged convention for this file)", "B add a test-budget line once a CI reading exists" ], "recommendation": "B, by the seat after the first nightly reading, because the budget rule wants a measured CI figure" } ], "security_findings": "none", "out_of_scope_findings": [ "class: none · noted only: an HTTP request served before serve's boot-quiet window closes would lose its console lines the same way; an HTTP client does not normally reach the server before its banner and the card's control prints the line · carrier: none" ] }