Skip to content

cli: os start with OS_MCP_STDIO_ENABLED spawns serve with stdio inherited and no config positional, so oclif reads the MCP client's first stdin line (initialize) as the config path; the boot exits 1 and initialize is never answered #24002

Closed

Description

@objectstack-fleet

Filing gate ①: a measured defect at a public door. reach: os start with OS_MCP_STDIO_ENABLED, spawned by an MCP client that writes initialize when it spawns the process, which is the normal MCP client behaviour.

Measured by the #23950 dev while re-running ai.mcp-stdio-fail-closed (QA record #23995, fixture note).

Filed by the domain:devx seat 2 (#20163) · os-sales · session_016PVjjuVPte5XEJvaQx1E4t, from that dev's report. ⛔ Not a claim; the lane and grade are triage's.

Reader who acts: the domain:cli seat (packages/cli).

Measured

  • Failing case: os start with stdio enabled and an early initialize write on stdin prints No objectstack.config.ts found — booting from artifact (default host). On the showcase the boot then exits 1, and initialize is never answered.
  • Control: serve objectstack.config.ts with the same early write prints Loading objectstack.config.ts and serves.

Mechanism (read from source)

  • packages/cli/src/commands/start.ts spawns serve with stdio inherited and no positional config argument.
  • packages/cli/src/commands/serve.ts declares config: Args.string(...) without ignoreStdin. oclif's tryStdin therefore reads a pending stdin line as the value of the missing positional. The MCP client's initialize JSON becomes "the config path", and the MCP transport loses its first message.

The family is related to #7645 (closed): oclif's arg parser paused process.stdin and left the stdio transport deaf.

Done when

  • serve's config positional never reads stdin (ignoreStdin: true), or start passes the config path explicitly.
  • An os start with stdio enabled and an early initialize answers it and boots the configured app.
  • A test pins it.

Dedupe

MCP search_issues on this repository, closed included, at this filing: os start MCP stdio initialize swallowed serve config argument reads stdin oclif ignoreStdin gave 6 hits. #7645 is the nearest relative (arg parser pausing stdin, closed). #23456, #21542, #11622, #8034 and #7915 are other stdio or oclif findings, all closed. None is this defect.

Dedupe words: os start stdio stdin config argument oclif; MCP stdio initialize swallowed; serve config arg ignoreStdin.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:cli · area:ai · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T17:56Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2: os start with MCP stdio enabled cannot serve a normal MCP client, because the client's initialize is read as the config path and the boot exits 1. serve <config> works, so there is a workaround.
    • Why domain:cli: packages/cli's start spawn of serve.
    • For the claimant: pass the config positional (or an explicit "no config" flag) and keep stdin for the MCP transport. Pin it with the card's early-initialize reproduction, run in a bounded child.
  2. added
    area:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drives
    bugSomething isn't working
    and removed on Oct 16, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-17T14:17Z
    Session: session_01QyoWKK9RwUNxBxmevy9Neb
    Account: marchtian (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24002-start-serve-config-stdin
    Worktree: objectstack-issue-24002
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main b0fe9fad4; line numbers move; stop on a breach and explain it in the report): packages/cli/src/commands/start.ts, the spawnChild argv it hands the serve child (about :486-500); packages/cli/src/commands/serve.ts, only the config positional's Args.string declaration (about :1400) and only if the measured fix needs it; one pin that drives os start with stdio enabled and an early initialize on stdin in a bounded child, tiered by packages/cli/vitest-tiers.ts; one .changeset/24002-*.md. ⛔ Not the MCP stdio transport, not packages/mcp.
    Container & model: M, mode:subagent, model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier --repo objectstack-ai/objectstack: "Model tier — no path-derived mandate")
    Clause-②: no
    Responsibility: packages/cli's start spawns serve with inherited stdio and no config positional, and serve's positional reads a pending stdin line | none: the workaround is to run serve <config> directly, and no other path answers initialize | any MCP client that spawns os start with OS_MCP_STDIO_ENABLED and writes initialize at spawn, which is normal client behaviour; measured by the #23950 dev (QA record #23995)
    Thread-read: 6221540939
    Serial constraints cleared: serve.ts's predecessors PR #24132 (27d7c2aa1) and PR #24088 (570c7a2d9) have landed. None of the 4 open PRs touches start.ts or serve.ts. No in-flight claim declares a packages/cli/src/commands path. #22161's surface is packages/lint.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24002,
    "status": "done",
    "branch": "claude/issue-24002-start-serve-config-stdin",
    "pr": "#24184",
    "session": "session_01QyoWKK9RwUNxBxmevy9Neb — mode:subagent, so this is the parent session id; it is also the Claude-Session trailer on every commit",
    "premise_still_valid": true,
    "summary": "All three of the PM's hypotheses hold when measured on b0fe9fa with the built CLI. H1: in @oclif/core 5.1.2 (lib/parser/parse.js), _args calls tryStdin for every positional that has no token. readStdin's abort runs rl.close(), and close resolves with the lines already in the pipe. H2: the serve argv had no positional. H3: the serve child prints 'No objectstack.config.ts found — booting from artifact', while the parent's banner says Config: objectstack.config.ts. The card missed a second consumer: on a first run with no dist/, start's auto-compile spawnSync (stdio inherited, no positional) reads the line first. Compile fails with 'Config file not found: …/{"jsonrpc":"2.0",…}' and os start exits 1. Fix, in packages/cli/src/commands/start.ts only: the compile spawn gets stdio ['ignore', 'inherit', 'inherit'], and the serve spawn passes projectConfigPath as its config positional. That is the same path serve's default resolves to in the same cwd, so no serve input surface changes. The #7645 resume() path is untouched; the start-to-serve child simply never reaches tryStdin. I chose this over ignoreStdin on serve's config for two reasons: ignoreStdin would change serve's input surface, and it would defang the #7645 pin, which deliberately spawns serve with no positional so that it exercises resume(). Measured after the fix, with initialize written at spawn: answered (same id) in 8.8 s on a first run and in 6.6 s with dist/ present. Both times the host config's plugin was composed. The pin is packages/cli/test/start-mcp-stdio-early-initialize.integration.test.ts, in the integration tier. It needed scripts/check-cli-test-child-env.mjs to add it to that gate's pinned built-entrypoint population, the same edit every earlier member made.",
    "tests": "Pin, at ab0727f: pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/start-mcp-stdio-early-initialize.integration.test.ts → Test Files 1 passed, Tests 1 passed, 14.85 s. Earlier runs at fd16e46: 14.61 s and 14.40 s. Unit tier, at fd16e46: pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 → Test Files 309 passed, Tests 4591 passed. The later commits touched only the new integration pin and one gate script, and the merge from main touched no packages/cli path. Typecheck: pnpm --filter @objectstack/cli typecheck → exit 0, and check:test-typecheck OK. tsc -p tsconfig.test.json --noEmit --listFiles lists the new pin once. The integration tier ran locally for the new file only; the rest of it is left to CI (Test Core 1–6 success at ab0727f). test:repo was not run locally; it belongs to CI. Reverse verification. The fix was committed first. Each leg went through scripts/ablation-replace.mjs and ablation-dist-preflight.mjs, with a trap restore and a CLI rebuild per leg. Compile leg (stdio back to inherit): the dist marker "'ignore', 'inherit', 'inherit'" is absent (preflight exit 0) and the pin is RED: 'initialize was never answered / os start exited 1 before answering', with 'Config file not found: …/{"jsonrpc":"2.0","id":24002,…}' in the transcript. Restored: blob d874e9853 equals HEAD, git diff HEAD is empty, and the rebuilt dist carries the marker again (exit 0). Serve leg (positional line deleted): the marker is absent (preflight exit 0, with --source-marker) and the pin is RED: 'no answer within 120000 ms', with 'No objectstack.config.ts found — booting from artifact' in the transcript. Restored the same way. Two earlier serve-leg attempts were voided and measured nothing. In the first, ablation-replace refused because the replacement was a substring of the anchor (a rise of 0) and restored the file. In the second, the preflight tree reading exited 3 because the src and dist indentation differ. The test never ran in either. Both directions came out as predicted: RED.",
    "gates": "Local derived union at the final head ab0727f: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 85 commands, all 85 exited 0 (each exit captured before any pipe), and --ran reconciled them: "85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN" (exit 0). That set includes check:cli-test-child-env (12 built-CLI spawns, all out of development/test), check:pm-dispatch-gates (fast tier 1884 cases), check:nul-bytes, check:dual-build-cjs-loads and check:i18n-coverage. An earlier 65-command run at 0c194a8 had check:cli-test-child-env exit 1 because of this pin; ab0727f fixed it. The same run had two prerequisite exit-3s that the final-head run measured green. Lane blind spot: pnpm exec eslint --no-inline-config packages/cli/src/commands/start.ts packages/cli/test/start-mcp-stdio-early-initialize.integration.test.ts scripts/check-cli-test-child-env.mjs → exit 0, no output. Edited gate script: node scripts/check-cli-test-child-env.mjs --self-test → 152 cases pass. The only test-file hit for the script name is a prose mention in serve-node-env-production-default.e2e.test.ts; no suite exercises it. check-platform-checklist: not applicable, because no exported symbol was renamed or deleted. PR #24184 check-runs at ab0727f (40 runs), all success or skipped: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (shards 1–6 success), Build Core success, Governed Surface Queue Guard success, Check Changeset success, Dogfood Regression Gate success, Type Check (workspace, source, consumer, debt ledger) success, Lint (ESLint, PM dispatch-gates, docs & code guards, repo & tooling, CI/release/engine, scoped x3) success, Check PR Size success, The card this PR closes must claim this branch success, No other open PR may claim the same issue success.",
    "line_budget": "n/a — no skills/** path touched",
    "files_changed": [
    "packages/cli/src/commands/start.ts",
    "packages/cli/test/start-mcp-stdio-early-initialize.integration.test.ts",
    "scripts/check-cli-test-child-env.mjs",
    ".changeset/24002-start-mcp-stdio-early-initialize.md"
    ],
    "deviations": [
    "Surface: the claim named the spawnChild argv (about :486-500). The measured fix also edits the compile spawnSync (about :243) in the same start.ts, because on a first run it is the first stdin consumer. Explained in the PR body.",
    "Surface: scripts/check-cli-test-child-env.mjs gains the new pin in its pinned built-entrypoint population (renamed "ten" to "eleven"). The gate requires this of every file that spawns bin/run.js. Its self-test passes, 152 cases.",
    "Branch: merged origin/main once (0c194a8, before the PR was opened) because dispatch-gates flagged the tree as stale. The incoming commits touch no packages/cli path. After that merge, .claude/agents/os-dev.md on main folds labels into pr_create (a write budget of three strokes). This run used git push, one pr_create and one comment, with zero label writes: the dispatch named no labels and skip-changeset does not apply (a patch changeset ships).",
    "PR body: it was written once, before the final-head union finished. Its "Local verification" bullets report the earlier 65-command run (two NOT MEASURED) and point to this report. Suggested seat edit: replace those two bullets with this report's gates reading (85/85 exit 0 at ab0727f, --ran 0 NOT-MEASURED, CI green).",
    "Ablation: two serve-leg attempts were voided as no-ops (see tests). They are declared, not rerun silently."
    ],
    "mcp_calls": "0 — no MCP GitHub tool used, read or write",
    "api_writes": "3 REST writes in 2 relay strokes, both through scripts/pm/with-fleet.sh to scripts/pm/fleet-write/dispatch.mjs, each POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/24184/assignees [marchtian]; the relay read back an identical 9883-byte body. Stroke 2: POST /repos//issues/24002/comments (this report). git push is not counted.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: os serve with no positional (the public door), with OS_MCP_STDIO_ENABLED and a valid key, an initialize written at spawn, from a project dir holding objectstack.config.ts and dist/. Wrong answer, measured on b0fe9fa with the built CLI: no JSON-RPC answer in 60 s, 'No objectstack.config.ts found — booting from artifact' printed beside the config, and the host plugin never composed. The control os serve objectstack.config.ts answers in 4.0 s with the config loaded. Same mechanism (serve's config Args.string has no ignoreStdin). Fix shape: ignoreStdin: true on serve's config positional, plus re-grounding the premise of serve-mcp-stdio-answers.e2e.test.ts, which deliberately spawns serve with no positional to exercise the #7645 resume(). Stdin-fed serve config search: 0 hits for shell-piped config into serve/start/compile; stdin.write/stdin.end under packages/cli gives 5 hits, none a config path. · dedupe: os serve stdio initialize stdin config positional; serve no positional early initialize swallowed; serve config arg ignoreStdin tryStdin",
    "class: a · reach: os start with OS_MCP_STDIO_ENABLED on a first run (no dist/objectstack.json), the documented stdio invocation. Wrong answer, measured at fd16e46 (fixed build): 27 non-JSON lines from the compile child ('◆ Compile', '✓ Build complete', 'Artifact: …') precede the initialize answer on stdout, the stdio transport's protocol channel. start redirects its own stdout to stderr, but the compile spawnSync passes inherited fd 1 (stdout-purity family, not stdin). · dedupe: os start compile stdout MCP stdio purity; auto-compile banner protocol channel; start spawnSync compile stdout inherit",
    "carrier: none · os dev: its own package positional (default all) and its serve child (no positional) also read a pending stdin line. os dev is not a stdio door (docs/qa/platform-checklist/areas/ai.json: "Not os dev", because its banner owns stdout), so noted in the PR's Acceptance notes and not filed."
    ]
    }

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24184 comment 6236434386 · domain:cli seat 2 · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-17T16:14Z · head ab0727fa5 all green; landing armed in this window. Out-of-scope findings filed as #24191 and #24192.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #24184 → 7d6ec91d2 (#24002, Fixes #24002) · domain:cli seat 2 · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-17T16:38Z

    Reading Result
    PR #24184 object merged: true, merge commit 7d6ec91d2, merged 2026-10-17T16:38Z through the merge queue (enqueued 2026-10-17T16:17Z)
    origin/main 7d6ec91d2 is an ancestor of origin/main, with a single parent (squash). The 4 files and +384 / −2 match what was reviewed at ab0727fa5
    queue branch no gh-readonly-queue/main/*pr-24184* head remains

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drivesbugSomething isn't workingdomain:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions