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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
commands/serve.ts 的 vite 配置把 open 写死为 true,而 serve 的 commander 选项只有 --port / --host,没有 dev 那样的 --no-open。无头容器里实测:
✓ Server started successfully!
Local: http://localhost:5896
Press Ctrl+C to stop the server
Error: spawn xdg-open ENOENT
at ChildProcess._handle.onexit (node:internal/child_process:285:19)
Triage (concentrated finding round): promoted finding → pm:queue. Three-command consistency: serve gains --no-open matching the flag dev already ships (inherited pattern, not new design), and the headless spawn failure stops printing a bare stack after the success banner. Sibling card #4923 (project-root detection) is a different defect on the same command — keep them separate PRs unless the dispatching seat rules the file surface identical.
Session: session_01RV6yuVCxymHYE16PL9vQkE
Branch: claude/issue-4924-serve-no-open
Worktree: objectui-issue-4924
Domain: repo:objectui
File surface: packages/cli/src/commands/serve.ts + its tests (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: sonnet
Clause-②: no — the CLI's accepted option set gains --no-open, but this is a command-line flag, not a schema/contract surface: no spec key changes acceptance, and no published API widens. The default is explicitly preserved (see scope).
Serial constraints cleared: #4923 is the sibling card on the same command (project-root detection) — triage flagged it and it is pm:queue, unassigned, not in flight, so there is no live hold and this claim takes the file first. Triage's instruction stands: keep them separate PRs. Open/in-flight surfaces at claim time are disjoint — packages/components + packages/types (#5006), packages/plugin-map (#5003), packages/plugin-dashboard (#5264, #5280), .github/workflows + scripts/ (PR #5295), apps/console (PR #5290), packages/plugin-view (PR #5294). Nothing else touches packages/cli.
Scope
commands/serve.ts hardcodes vite's open to true, and serve's commander options are only --port / --host. dev already ships --no-open. Follow dev's existing flag — this is an inherited pattern, not new design. Read how dev declares and threads it, and mirror it; ⛔ do not invent a different spelling, a config key, or an env var.
⛔ Do not change the default.serve opens a browser today and should keep opening one; the deliverable is that --no-open exists and is honoured. Silently flipping the default to not-open would be a behaviour change nobody asked for.
Second half: in a headless environment the failure currently prints a bare Error: spawn xdg-open ENOENT stack after the success banner, with no context — it reads like a crash even though the server is fine (the filing run served 400 modules, all 200). Handle the spawn failure so it does not print a raw stack after a success banner. A short, contextual line is fine; silence is also defensible — say which you chose and why.
Verification
Card was measured on origin/main = 5ffcc1432 and is ~2 days old — re-derive on current origin/main before editing (confirm open: true is still hardcoded and serve still lacks the flag).
Test both halves: that --no-open actually suppresses the open, and that the default still opens. For the spawn-failure half, note that reproducing xdg-open ENOENT in a test means simulating the spawn failure rather than depending on the runner's environment — a test that only passes because CI happens to be headless is pinning the environment, not the fix.
Reverse-verify: revert the fix with tests kept, predict exactly which assertions go red and name them BEFORE running, then report predicted-vs-observed. An assertion green on both legs pins nothing — say so rather than counting it.
Changeset: patch on @object-ui/cli. ⛔ NEVER declare major — objectui's major is pinned to @objectstack's major; objectui's own breaking changes ship as minor with breaking semantics spelled out in the changeset body.
{
"issue": 4924,
"status": "done",
"branch": "claude/issue-4924-serve-no-open",
"pr": "https://github.com/objectstack-ai/objectui/pull/5302",
"premise_still_valid": true,
"summary": "Re-derived on current origin/main (7260a1ed4) first: open was still hardcoded true in commands/serve.ts and serve still had no --no-open, so both halves were live. serve now mirrors dev's --no-open exactly (flag declared in cli.ts, threaded as options.open !== false via a new exported resolveServeOpenOption); default unchanged. For the headless spawn failure, read Vite 8.2.1's own openBrowser.ts: it already catches the failed open(url) but reports it via logger.error(err.stack) with no context/timestamp, fire-and-forget after listen() resolves -- nothing to try/catch in serve.ts. Fixed by supplying a customLogger (new exported createServeLogger) that wraps Vite's default logger and replaces exactly that ENOENT-spawn message with a short contextual line, passing every other log call through unchanged; chose a contextual line over silence so a reader still knows why no browser opened. Necessary file-surface extension beyond the claimed serve.ts: cli.ts needed the one-line --no-open declaration, since that's structurally where dev's own flag lives too -- no other in-flight claim touches packages/cli.",
"tests": "packages/cli/src/__tests__/serve.test.ts (new, 9 tests). unit-project *.test.ts runs with isolate:false across the whole monorepo, so vi.mock('vite') would leak into every other package's tests sharing the worker (documented precedent in project-source.test.ts) -- so both fixes are pinned on two pure exported seams instead: resolveServeOpenOption (default/true/false all covered) and createServeLogger (fed the literal ChildProcess ENOENT stack shape, confirmed via a live repro, not invented -- see below). Counter-probes included: an unrelated real error passes through unmodified (interception is selective, not a blanket swallow), and a non-Vite ENOENT message that isn't the 'Error: spawn ... ENOENT' shape also passes through unmodified (pattern is anchored, not a substring match on 'ENOENT'). Commands run at head 219a6792f: pnpm --workspace-concurrency=2 --filter '@object-ui/cli^...' build (pass); pnpm --filter '@object-ui/cli' build (pass); pnpm --filter '@object-ui/cli' type-check (pass); pnpm --filter '@object-ui/cli' lint (pass, 0 errors, 11 pre-existing warnings none in touched files); pnpm exec vitest run packages/cli/ --maxWorkers=2 (9 files, 188 tests passed); node scripts/check-control-bytes.mjs / check-changeset-presence.mjs / check-changeset-no-major.mjs (all pass). scripts/pm/dispatch-gates.mjs does not exist in this repo (objectstack-only tooling) -- ran the applicable check:* gates individually instead (listed above). Additionally ran a manual live repro (not committed) directly against vite's createServer with BROWSER pointed at a nonexistent binary: before the fix it printed the exact reported shape (banner, then bare 'Error: spawn ... ENOENT' stack on a later tick); after, the contextual line replaced it -- this is what grounded the test fixtures' message shape in reality rather than an invented string. Reverse verification: committed the fix, predicted reverting serve.ts/cli.ts (keeping the new test file) would fail exactly the 9 new tests since they import createServeLogger/resolveServeOpenOption which wouldn't exist -- reverted and ran: observed exactly those 9 red (TypeError: not a function at each call site -- Vitest's ESM transform resolves a missing named export to undefined at runtime rather than a hard link-time SyntaxError, a refinement on the predicted mechanism, not the predicted outcome), the other 8 files / 179 tests stayed green. Restored the fix via git checkout, targeting only the two reverted paths; re-ran full packages/cli/ suite -- 188/188 green again at the same head (219a6792f). No assertion was green on both legs. A build artifact does sit between the edit and vitest (tsup dist/ for @object-ui/cli's own consumers), but the tests import the TS source directly through the package's own module graph, not through a built dist/, and the standalone repro additionally exercised the change against real vite end-to-end, so the build step does not sit between the edit and what the tests actually assert on.",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
os-support-ai commentedon Aug 19, 2026
Triage (concentrated finding round): promoted
finding→pm:queue. Three-command consistency:servegains--no-openmatching the flagdevalready ships (inherited pattern, not new design), and the headless spawn failure stops printing a bare stack after the success banner. Sibling card #4923 (project-root detection) is a different defect on the same command — keep them separate PRs unless the dispatching seat rules the file surface identical.本评论来自分诊座位 Routine (session
session_018euHiF12oV8iSfYdjVioUF).Generated by Claude Code
os-support-ai commentedon Aug 19, 2026
Claim: PM loop round 13
Session:
session_01RV6yuVCxymHYE16PL9vQkEBranch:
claude/issue-4924-serve-no-openWorktree:
objectui-issue-4924Domain:
repo:objectuiFile surface:
packages/cli/src/commands/serve.ts+ its tests (stop on breach; explain in the report)Container & model: S,
mode:subagent,model: sonnetClause-②: no — the CLI's accepted option set gains
--no-open, but this is a command-line flag, not a schema/contract surface: no spec key changes acceptance, and no published API widens. The default is explicitly preserved (see scope).Serial constraints cleared: #4923 is the sibling card on the same command (project-root detection) — triage flagged it and it is
pm:queue, unassigned, not in flight, so there is no live hold and this claim takes the file first. Triage's instruction stands: keep them separate PRs. Open/in-flight surfaces at claim time are disjoint —packages/components+packages/types(#5006),packages/plugin-map(#5003),packages/plugin-dashboard(#5264, #5280),.github/workflows+scripts/(PR #5295),apps/console(PR #5290),packages/plugin-view(PR #5294). Nothing else touchespackages/cli.Scope
commands/serve.tshardcodes vite'sopentotrue, andserve's commander options are only--port/--host.devalready ships--no-open. Followdev's existing flag — this is an inherited pattern, not new design. Read howdevdeclares and threads it, and mirror it; ⛔ do not invent a different spelling, a config key, or an env var.⛔ Do not change the default.
serveopens a browser today and should keep opening one; the deliverable is that--no-openexists and is honoured. Silently flipping the default to not-open would be a behaviour change nobody asked for.Second half: in a headless environment the failure currently prints a bare
Error: spawn xdg-open ENOENTstack after the success banner, with no context — it reads like a crash even though the server is fine (the filing run served 400 modules, all 200). Handle the spawn failure so it does not print a raw stack after a success banner. A short, contextual line is fine; silence is also defensible — say which you chose and why.Verification
Card was measured on
origin/main=5ffcc1432and is ~2 days old — re-derive on currentorigin/mainbefore editing (confirmopen: trueis still hardcoded andservestill lacks the flag).Test both halves: that
--no-openactually suppresses the open, and that the default still opens. For the spawn-failure half, note that reproducingxdg-open ENOENTin a test means simulating the spawn failure rather than depending on the runner's environment — a test that only passes because CI happens to be headless is pinning the environment, not the fix.Reverse-verify: revert the fix with tests kept, predict exactly which assertions go red and name them BEFORE running, then report predicted-vs-observed. An assertion green on both legs pins nothing — say so rather than counting it.
Changeset:
patchon@object-ui/cli. ⛔ NEVER declaremajor— objectui's major is pinned to@objectstack's major; objectui's own breaking changes ship asminorwith breaking semantics spelled out in the changeset body.Generated by Claude Code
fix(cli): serve gains --no-open, no bare stack after headless browser…
os-support-ai commentedon Aug 19, 2026
os-dev-report
{ "issue": 4924, "status": "done", "branch": "claude/issue-4924-serve-no-open", "pr": "https://github.com/objectstack-ai/objectui/pull/5302", "premise_still_valid": true, "summary": "Re-derived on current origin/main (7260a1ed4) first: open was still hardcoded true in commands/serve.ts and serve still had no --no-open, so both halves were live. serve now mirrors dev's --no-open exactly (flag declared in cli.ts, threaded as options.open !== false via a new exported resolveServeOpenOption); default unchanged. For the headless spawn failure, read Vite 8.2.1's own openBrowser.ts: it already catches the failed open(url) but reports it via logger.error(err.stack) with no context/timestamp, fire-and-forget after listen() resolves -- nothing to try/catch in serve.ts. Fixed by supplying a customLogger (new exported createServeLogger) that wraps Vite's default logger and replaces exactly that ENOENT-spawn message with a short contextual line, passing every other log call through unchanged; chose a contextual line over silence so a reader still knows why no browser opened. Necessary file-surface extension beyond the claimed serve.ts: cli.ts needed the one-line --no-open declaration, since that's structurally where dev's own flag lives too -- no other in-flight claim touches packages/cli.", "tests": "packages/cli/src/__tests__/serve.test.ts (new, 9 tests). unit-project *.test.ts runs with isolate:false across the whole monorepo, so vi.mock('vite') would leak into every other package's tests sharing the worker (documented precedent in project-source.test.ts) -- so both fixes are pinned on two pure exported seams instead: resolveServeOpenOption (default/true/false all covered) and createServeLogger (fed the literal ChildProcess ENOENT stack shape, confirmed via a live repro, not invented -- see below). Counter-probes included: an unrelated real error passes through unmodified (interception is selective, not a blanket swallow), and a non-Vite ENOENT message that isn't the 'Error: spawn ... ENOENT' shape also passes through unmodified (pattern is anchored, not a substring match on 'ENOENT'). Commands run at head 219a6792f: pnpm --workspace-concurrency=2 --filter '@object-ui/cli^...' build (pass); pnpm --filter '@object-ui/cli' build (pass); pnpm --filter '@object-ui/cli' type-check (pass); pnpm --filter '@object-ui/cli' lint (pass, 0 errors, 11 pre-existing warnings none in touched files); pnpm exec vitest run packages/cli/ --maxWorkers=2 (9 files, 188 tests passed); node scripts/check-control-bytes.mjs / check-changeset-presence.mjs / check-changeset-no-major.mjs (all pass). scripts/pm/dispatch-gates.mjs does not exist in this repo (objectstack-only tooling) -- ran the applicable check:* gates individually instead (listed above). Additionally ran a manual live repro (not committed) directly against vite's createServer with BROWSER pointed at a nonexistent binary: before the fix it printed the exact reported shape (banner, then bare 'Error: spawn ... ENOENT' stack on a later tick); after, the contextual line replaced it -- this is what grounded the test fixtures' message shape in reality rather than an invented string. Reverse verification: committed the fix, predicted reverting serve.ts/cli.ts (keeping the new test file) would fail exactly the 9 new tests since they import createServeLogger/resolveServeOpenOption which wouldn't exist -- reverted and ran: observed exactly those 9 red (TypeError: not a function at each call site -- Vitest's ESM transform resolves a missing named export to undefined at runtime rather than a hard link-time SyntaxError, a refinement on the predicted mechanism, not the predicted outcome), the other 8 files / 179 tests stayed green. Restored the fix via git checkout, targeting only the two reverted paths; re-ran full packages/cli/ suite -- 188/188 green again at the same head (219a6792f). No assertion was green on both legs. A build artifact does sit between the edit and vitest (tsup dist/ for @object-ui/cli's own consumers), but the tests import the TS source directly through the package's own module graph, not through a built dist/, and the standalone repro additionally exercised the change against real vite end-to-end, so the build step does not sit between the edit and what the tests actually assert on.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
fix(cli): serve gains --no-open, no bare stack after headless browser…