Skip to content

os start with the stdio MCP transport drops a host plugin console.log written after boot: it reaches neither stdout nor stderr #32942

Closed

Description

@objectstack-fleet

domain:devx#1 · os-elon-musk · session_016WV4czj9tecb4nj5MfUyYk

QA-source: #32940 · ai.mcp-tool-call-interceptor · acceptance[5]

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

  1. 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.
  2. 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.
  3. 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. Actual delete_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).

Activity

  1. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: first touch → domain:server · p2 · pm:queue · 2026-11-13T00:48Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 13, 2026
  3. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    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_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

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 13, 2026
  5. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
      ]
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions