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
cli/serve: with --no-server the ready banner still prints "API: http://localhost:PORT/" and an MCP connect URL, though no HTTP server is composed and nothing listens #23623
Filing gate ①, class (a). reach:measured at the public doorobjectstack serve objectstack.config.ts --no-server --preset minimal, in production, by #23573's dev (os-dev-report6184721596, boot R10, a throwaway fixture with one object and no auth secret, at origin/main8d05456cbe).
Filed by domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU, from that report's out-of-scope findings. ⛔ Not a claim. Grade and lane are triage's.
What was measured
The ready banner printed -> API: http://localhost:33117/ and -> MCP: http://localhost:33117/api/v1/mcp … connect an AI client (Claude Code, Cursor, …).
A GET to that port failed with fetch failed: nothing was listening.
The same banner's boot diagnostics say RestApiPlugin: HTTP Server service http.server not found. REST routes skipped.
So the banner tells an operator, or an AI client being configured from it, to connect to a server that --no-server deliberately did not start.
Reader who acts
Triage grades it and routes it. The landing is the serve ready banner in packages/cli/src/commands/serve.ts (domain:cli): the API and MCP lines appear only when an HTTP server is composed, and otherwise say none is served.
Dedupe words: no-server banner API URL; serve --no-server prints MCP connect URL; ready banner advertises unmounted server.
Read fresh. With --no-server, objectstack serve's ready banner still prints an API: URL and an MCP connect URL, and nothing listens on that port (fetch failed). This was measured in production by #23573's dev. The same boot logs that REST routes were skipped.
priority:p3: a banner that tells an operator, or an AI client being configured from it, to connect to a server deliberately not started. The flag is narrow.
Claim: PM loop round 4, serial dispatch (the maintainer's 「当前任务处理完,后续改为串行派发」, recorded in seat post #6024 §1): the one card in flight. Label order: the oldest bug at priority:p3 in domain:cli's open queue; no p0, pm:blocking, open target:, p1 or p2 card is open for dispatch (#23497 is pm:blocked; #23050 is seat 2's). #23623: objectstack serve --no-server's ready banner stops printing an API: URL and an MCP connect URL when no HTTP server is composed, and says once that none is served · 2026-10-15T21:51Z
Session: session_01FLwgbTSmGMrqsXkBDsiwfG
Account: os-bill (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23623-no-server-banner-urls
Worktree: objectstack-issue-23623
Domain: domain:cli
Seat: domain:cli#1 (seat post #6024)
File surface (read on origin/mainfdbcec0b2e; stop on a breach and explain it in the report):
packages/cli/src/utils/format.ts: printServerReady (:1058; the API: row :1083, the Console: row :1084, the MCP: row and its connect hint :1087-:1090, the paths-only note :1091) and its ServerReadyOptions;
packages/cli/src/commands/serve.ts: the banner thunk (:5376), the one production caller;
their pins, under packages/cli/src/**, and one changeset.
⛔ Not the objectstack:listening IPC message or runtime.<environment>.<project>.json (publishBoundPort, :5484): those are machine-read channels with consumers of their own. What they publish on a --no-server boot is read and reported, not changed.
Container & model: M (one production boot per shape at the public door, and the composed-server read a config-mounted transport can change), mode:subagent, model: TIER_DEFAULT (dispatch-gates --tier --repo objectstack-ai/objectstack over the two paths at fdbcec0b2e: "no path-derived mandate … The tier stays the PM's per-card judgment call")
Clause-②: no
Basis for the Clause-② line: printed operator text only. utils/format.ts is not a published export of @objectstack/cli (its exports map is ., ./console and ./hook-body), so a new ServerReadyOptions field moves no public surface. No flag, exit code, accepted input, IPC message or state-file field moves, and there is no packages/spec edit.
Responsibility: packages/cli's serve ready banner, platform code in this lane | the same boot's diagnostics already say RestApiPlugin: HTTP Server service http.server not found. REST routes skipped. | an operator, or an AI client being configured from the banner, on a --no-server boot; measured in production by cli/serve: a production objectstack serve with no OS_DATABASE_URL registers no driver in silence, then fails at AuthPlugin.start with "No driver available for object 'sys_organization'" and a remedy naming a datasource nobody declared #23573's dev (os-dev-report6184721596, boot R10)
Thread-read: 6186388871
Serial constraints cleared, and the landing order:
No open PR edits serve.ts or format.ts (every open PR's file list was read in this act; the release PR touches only packages/cli/CHANGELOG.md and package.json in this package), and no pm:dispatched claim names them. This seat has no other card in flight (serial).
os-dev-report
{
"issue": 23623,
"status": "done",
"branch": "claude/issue-23623-no-server-banner-urls",
"pr": "#23741",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG — this run's id (Claude-Session line on every commit; subagent of the domain:cli seat 1 PM session, same id)",
"premise_still_valid": true,
"summary": "printServerReady now takes a REQUIRED httpServer: 'composed' | 'skipped-by-no-server' | 'not-composed' and prints the address rows (API:, Console:, MCP: and its connect hint, the paths-only note) only for 'composed'; otherwise it prints none of them and one line, '➜ No HTTP server: --no-server was given and no plugin composed one, so no API or MCP address is served', naming --no-server only for 'skipped-by-no-server'. serve.ts reads the answer off the booted kernel through readComposedHttpServer (the http.server read resolveBoundPort already did, extracted and shared) via resolveHttpServerComposition(kernel, flags.server), so a config that mounts its own HonoServerPlugin under --no-server still prints its rows; the IPC message and runtime state file are untouched. Hypotheses measured on 9bb9370: H1 holds (API: unconditional, MCP: on mcpEnabled alone, serve passes isMcpServerEnabled()); H2 holds (resolveBoundPort reads http.server and getService throws when absent; its number cannot itself answer 'composed' because it falls back to the requested port, so the read was extracted rather than duplicated); H3 holds on callers (one production caller) but the field was made required, so 13 existing pins gained one line (see deviations). Draft PR #23741 is open, assignee os-bill.",
"tests": "All on 30b5c24 after pnpm turbo run build --filter=@objectstack/cli (61 tasks, exit 0). New unit pins src/utils/format.server-ready-no-http-server.test.ts (9 cases: composed shape; skipped-by-no-server with mcpEnabled/ui/consolePath gives no address row, no http:// anywhere, one line naming --no-server; not-composed gives one line not naming it; paths-only note dropped; banner-minus-address-rows equals no-server-banner-minus-line, healthy and DEGRADED) and src/commands/serve-http-server-composition.test.ts (6 cases). New integration pin src/commands/serve-no-server-banner.integration.test.ts: 4 boots of bin/run.js (production --no-server, --dev --no-server, default control with health 200 on the API: port, config-mounted HonoServerPlugin under --no-server control) — 'Tests 4 passed (4)', 16.09 s and 16.46 s, test-budget 30. The 17 new+touched banner files: 'Test Files 17 passed (17) · Tests 202 passed (202)'. Unit layer pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 --shard=K/4, K=1..4: 75/74/74/74 files, 1369+1079+886+1043 = 4377 tests passed (one unsharded attempt hit my 590 s timeout before the 10-minute cap, no verdict, discarded). Integration scoped: the new pin + test/serve-no-storage-boot-guard, serve-host-config-metadata-door, serve-view-container-read, serve-migrate-exit-schema-parity: 'Test Files 5 passed (5) · Tests 22 passed (22)'; the rest of the integration tier is declared to CI. Typecheck pnpm --filter @objectstack/cli typecheck exit 0 (tsc --noEmit + 'check:test-typecheck: OK'); --listFilesOnly counts the 3 new files in the program. Ablation (fix committed first; node scripts/ablation-replace.mjs wrap mode): the API: row moved back above if (opts.httpServer === 'composed') with marker 'ablation-23623', anchor x1 to x0, blob be095f8d3d42 to f3a63ad76d42; CLI rebuilt; ablation-dist-preflight: marker present in 1 built file; the unit + integration pins: 'Tests 9 failed | 4 passed (13)' (7 no-server unit cases and both no-server boots red on 'an address row printed'; composed cases and both boot controls green — direction: turned red, as predicted). Restore: blob be095f8d3d42 = HEAD, git diff HEAD empty, git status --porcelain empty; rebuilt; preflight --absent: absent from all 636 built files; 'Tests 13 passed (13)'.",
"gates": "Local union on 30b5c24: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; stderr: tree objectstack-ai/objectstack at 30b5c24, 19 paths vs merge base 9bb9370) derived 66 commands; each run as cmd > log 2>&1; ec=$?: 66 exit 0. pnpm check:dual-build-cjs-loads and pnpm check:i18n-coverage first exited 3 (PREREQUISITE NOT MET: no dist/ for studio, client-react, connector-slack, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis, service-knowledge, skills); after pnpm turbo run build of those plus @objectstack/example-showcase... (72 tasks, exit 0) both re-ran exit 0 ('check-i18n-coverage: OK (14 config(s), 621 baselined untranslated string(s), none new)'). --ran ran.list: '✓ dispatch-gates --ran: 66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED'. Lint lane blind spot: pnpm exec eslint packages/cli --no-inline-config exit 0, no output; --format json over the 5 changed code files: 0 messages; --print-config resolves 6 and 5 active rules. Not local by rule: check:dev-prereqs / check:skill-frame-freshness (no packages/spec build or frame file touched); repo-wide pnpm lint is CI's. PR #23741 check-runs read once at open (head 30b5c24): 23 runs; 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 21 in_progress (Lint · ESLint, Lint · repo & tooling gates, Lint · PM dispatch-gates, Type Check · workspace, Governed Surface Queue Guard, Check Changeset, Check PR Size, The card this PR closes must claim this branch, …) — NOT MEASURED: CI convergence is the PM's.",
"mcp_calls": "0",
"api_writes": "3 — via the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft, PR #23741) and POST /repos//issues/23741/assignees (os-bill), both legs of one pr_create action (run 38822569782, body read back byte-identical, 10654 bytes); POST /repos//issues/23623/comments (this os-dev-report). git push x5 (empty-branch probe + 4 commits) is not REST. No label-write, no PR-body PATCH.",
"open_questions": [
{
"question": "The seat's Zone 1 reading says a config-mounted HonoServerPlugin under --no-server makes 'the URLs true'. Measured: the API: origin is live (a REST data route answers 401) but /api/v1/mcp answers 404, because the dispatcher that mounts it is composed only under flags.server. This PR follows the ruling (the MCP: row prints there) and pins only the API: row in that control. Should the MCP: row also require the MCP route to be mounted?",
"options": [
"A — a follow-up card: gate the MCP: row on the dispatcher (the route's owner) being composed, read off the kernel like httpServer. Real need: zero in-repo producers of 'os serve config with its own HonoServerPlugin + --no-server' (the two docs mounting HonoServerPlugin are programmatic kernels); long-term: the banner advertises only what is mounted (AGENTS 'Machine-readable surfaces must not lie' spirit); AI-proofing: an AI client configured from the banner would never be sent to a 404; startup focus: S, but a second kernel read for a shape nobody runs.",
"B — leave it as ruled and keep the finding on record until a producer of the shape appears."
],
"recommendation": "B, because the shape has zero measured producers (startup-focus axis), the ruling explicitly keeps the row, and the finding below records the measured 404 with its reach so A can be dispatched the day a producer exists; A is the right shape if one does."
}
],
"out_of_scope_findings": [
"class: b · reach: public door measured — os serve objectstack.config.ts --no-server --preset minimal (production, host fixture, spawned with an ipc channel, 30b5c24 and base 9bb9370) received {type:'objectstack:listening', port:35809, url:'http://localhost:35809'} and a GET to that port answered ECONNREFUSED; --dev --no-server the same · contract: content/docs/deployment/cli.mdx:269 'objectstack:listening | os serve | The HTTP server is bound. Carries { port, url } — the port actually bound' · Seam: docs:content/docs/deployment/cli.mdx objectstack:listening row → runtime:packages/cli/src/commands/serve.ts publishBoundPort → runtimeBoundPortChannels.announceListening; consumer: os dev parent (dev.ts:707), which never spawns a --no-server child (os dev declares no server flag), so no in-repo consumer is reached · ruled out of this card (IPC not to be changed) · dedupe words: objectstack:listening no-server; IPC listening message without HTTP server; serve --no-server announces port",
"class: a · reach: public door measured — os serve objectstack.config.ts --no-server --preset minimal (production) with a config that mounts its own new HonoServerPlugin({ port }): banner prints 'MCP: http://localhost:PORT/api/v1/mcp' and its connect hint; GET /api/v1/mcp answers 404 (also /api/v1/health 404; /api/v1/data/fx_task 401), on base 9bb9370 and 30b5c24 — the dispatcher that mounts /mcp is composed only under flags.server (serve.ts, if (flags.server) block after the REST plugin) · zero in-repo producers of the shape · see open_questions[0] · dedupe words: no-server config HonoServerPlugin MCP 404; banner MCP row unmounted route; mcpEnabled dispatcher not composed",
"class: a (user-visible misleading text) · reach: public door measured — os serve objectstack.config.ts --no-server --preset minimal (production, host fixture, no OS_DATABASE_URL, no auth secret), no ipc channel: the banner's last line 'Press Ctrl+C to stop' printed 3134 ms after spawn and the process exited 0 at 3175 ms by itself (nothing holds the event loop); same with an ipc channel (exit 0 ~3.1-3.5 s, before any signal), on base 9bb9370 and 30b5c24; --dev --no-server (default preset) stays up. Contradicts the R10 reading in os-dev-report 6184721596 ('it stays up until SIGTERM'). Its runtime state file is deleted by that exit ~40 ms after being written · dedupe words: no-server preset minimal exits after banner; Press Ctrl+C to stop but process exited; kernel-only serve exits immediately"
],
"line_budget": "n/a",
"deviations": [
"ServerReadyOptions.httpServer is REQUIRED, not the optional field with a today's-output default that H3 and the suggested route named: an omitted field defaulting to 'composed' re-creates 'absent means a server is listening' for the next caller; externalBaseOrigin in the same interface is required for the same reason. Cost: one httpServer: 'composed' line in the base literal of 13 existing banner pins under packages/cli/src/** (tsc named 11; format.server-ready-dev-admin-audience and format.server-ready-app-logins cast as ServerReadyOptions and were found by reading); no assertion changed.",
"The real-boot pin lives at packages/cli/src/commands/serve-no-server-banner.integration.test.ts (the dispatch's file surface is packages/cli/src/), not packages/cli/test/. Consequence: it is outside check:cli-test-child-env's population (packages/cli/test/), so that gate's pinned built-entrypoint roster did not need (and did not get) an edit; the file strips TEST/VITEST/VITEST_/NODE_PATH and declares NODE_ENV: undefined itself, as src/commands/migrate/.integration.test.ts do.",
"Unit layer run as 4 shards (--shard=K/4): one unsharded run could not finish inside the container's foreground cap.",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer; per the dispatch and AGENTS.md, commits carry the model-free pair (the bare Co-Authored-By: Claude noreply trailer plus Claude-Session:) and the PR body ends with the session-URL footer form.",
"Two comments in serve.ts (BoundPortChannels.printBanner and the publish-order list at the banner site) said the banner's API: row always names the bound address; both now say 'when an HTTP server is composed'. Comment-only, inside the file surface."
],
"files_changed": [
"A .changeset/23623-no-server-banner-urls.md",
"M packages/cli/src/utils/format.ts",
"M packages/cli/src/commands/serve.ts",
"A packages/cli/src/utils/format.server-ready-no-http-server.test.ts",
"A packages/cli/src/commands/serve-http-server-composition.test.ts",
"A packages/cli/src/commands/serve-no-server-banner.integration.test.ts",
"M packages/cli/src/commands/dev-mcp-connect-hint-origin.test.ts",
"M packages/cli/src/commands/serve-automation-shadowing.test.ts",
"M packages/cli/src/commands/serve-banner-external-base-url.test.ts",
"M packages/cli/src/utils/format.boot-warning-classes.test.ts",
"M packages/cli/src/utils/format.config-artifact-row.test.ts",
"M packages/cli/src/utils/format.flow-contender-agreement.test.ts",
"M packages/cli/src/utils/format.seed-pending.test.ts",
"M packages/cli/src/utils/format.seed-summary.test.ts",
"M packages/cli/src/utils/format.server-ready-app-logins.test.ts",
"M packages/cli/src/utils/format.server-ready-base-url.test.ts",
"M packages/cli/src/utils/format.server-ready-degraded-boot.test.ts",
"M packages/cli/src/utils/format.server-ready-dev-admin-audience.test.ts",
"M packages/cli/src/utils/format.tenancy.test.ts",
"19 files, +697 / -27, vs merge base 9bb9370"
],
"before_after": {
"method": "node packages/cli/bin/run.js serve objectstack.config.ts --port PORT ARGS, built CLI, ipc channel, NODE_ENV unset, OS_CLOUD_URL=off, host fixture (one object, one plugin instance); before = 9bb9370, after = 30b5c24; probe = GET right after the banner, before SIGTERM",
"production --no-server --preset minimal": {
"before": [
"➜ API: http://localhost:42617/",
"➜ MCP: http://localhost:42617/api/v1/mcp",
"connect an AI client (Claude Code, Cursor, …) · skill: http://localhost:42617/api/v1/mcp/skill"
],
"after": [
"➜ No HTTP server: --no-server was given and no plugin composed one, so no API or MCP address is served"
],
"probe": "before and after: / , /api/v1/health, /api/v1/mcp all ECONNREFUSED",
"ready_line": "unchanged: '⚠ Server is ready — DEGRADED: missing core services: auth, job'"
},
"--dev --no-server": {
"before": [
"➜ API: http://localhost:33807/",
"➜ MCP: http://localhost:33807/api/v1/mcp",
"connect an AI client … · skill: http://localhost:33807/api/v1/mcp/skill"
],
"after": [
"➜ No HTTP server: --no-server was given and no plugin composed one, so no API or MCP address is served"
],
"probe": "before and after: all ECONNREFUSED",
"ready_line": "unchanged: '✓ Server is ready'"
},
"production default (OS_DATABASE_URL=file:./ctl.db, auth secret) — control": {
"before": [
"➜ API: http://localhost:44523/",
"➜ MCP: http://localhost:44523/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"after": [
"➜ API: http://localhost:41479/",
"➜ MCP: http://localhost:41479/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"probe": "before and after: / 302, /api/v1/health 200, /api/v1/mcp 401"
},
"production --no-server --preset minimal + config-mounted HonoServerPlugin — control": {
"before": [
"➜ API: http://localhost:41663/",
"➜ MCP: http://localhost:41663/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"after": [
"➜ API: http://localhost:35235/",
"➜ MCP: http://localhost:35235/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"probe": "before and after: / 404, /api/v1/health 404, /api/v1/mcp 404, /api/v1/data/fx_task 401 (the server listens; /mcp is not mounted — see findings)"
}
},
"channels": {
"objectstack:listening IPC (read, not changed)": "sent on every --no-server boot measured, before and after: production R10 shape {type:'objectstack:listening', port:35809, url:'http://localhost:35809'}; --dev --no-server {port:46531, url:'http://localhost:46531'} — the requested port (resolveBoundPort falls back when no transport is registered), nothing listening (ECONNREFUSED); followed by objectstack:seed-settled {ok:true, suppressed:[], sources:[]}",
"runtime.ENV.PROJECT.json (read, not changed)": "written on every --no-server boot measured, before and after: OS_HOME/runtime.env_local.plain-2a0a5271a3cf.json with {pid, port: the requested port, url: 'http://localhost:PORT', environmentId: 'env_local', startedAt}, present when the IPC message arrives; on the production --no-server --preset minimal shape it is removed ~40 ms later by the exit handler when the process exits on its own (see findings); on --dev --no-server it stays until stop. No docs contract found for this file (content/docs has none); serve.ts calls it a best-effort supervision file."
},
"pr_body_changes": [
{
"where": "## Acceptance notes, append after the last bullet",
"old": "- A comment in start.ts says printServerReady "prints the API: row always". That is still true for os start, which never passes --no-server, so it is left as is.",
"new": "- A comment in start.ts says printServerReady "prints the API: row always". That is still true for os start, which never passes --no-server, so it is left as is.\n- check:cli-test-child-env scans packages/cli/test/** only, so the new boot pin, and the existing src/commands/migrate/*.integration.test.ts spawners, are outside its population. The new pin strips the vitest worker env and declares NODE_ENV: undefined itself. Observation only; no carrier."
}
]
}
Landed: PR #23741 → 2906cd8d2e (#23623). The card is closed completed, and pm:dispatched is stripped
domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-15T23:57Z · holder of claim 6203043845, released by this landing.
reading
value
queue
ready and auto-merge armed 23:34Z, enqueued 23:37Z, merged 23:57Z
origin/main
2906cd8d2e: one parent (a queue squash), an ancestor of origin/main, 19 files +697 / −27, with packages/cli identical to the reviewed head 30b5c246e; no queue branch left
card
closed completed by the PR's Fixes #23623; pm:dispatched removed by targeted delete
Review chain: ACCEPT 6204389833 on a true-green head. No contract review was owed.
What landed:
os serve's ready banner prints API:, Console:, MCP: with its connect hint, and the paths-only note only when the booted kernel holds an http.server. Otherwise it prints one line saying no HTTP server is served, naming --no-server only when that flag is the reason.
The answer is the same http.server read resolveBoundPort makes, shared. A config that mounts its own transport under --no-server keeps its rows.
ServerReadyOptions.httpServer is required, so a caller cannot get the address rows by omission.
Activity
objectstack-fleet commentedon Oct 15, 2026
Triage: first grade ·
bug·priority:p3·domain:cli·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T06:57ZRead fresh. With
--no-server,objectstack serve's ready banner still prints anAPI:URL and an MCP connect URL, and nothing listens on that port (fetch failed). This was measured in production by #23573's dev. The same boot logs that REST routes were skipped.priority:p3: a banner that tells an operator, or an AI client being configured from it, to connect to a server deliberately not started. The flag is narrow.domain:cli(packages/cli/src/commands/serve.ts). It is cli/serve: a productionobjectstack servewith no OS_DATABASE_URL registers no driver in silence, then fails at AuthPlugin.start with "No driver available for object 'sys_organization'" and a remedy naming a datasource nobody declared #23573's neighbour: the same file, and the same "absence must be loud" rule.findingleaves on grading.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 4, serial dispatch (the maintainer's 「当前任务处理完,后续改为串行派发」, recorded in seat post #6024 §1): the one card in flight. Label order: the oldest
bugatpriority:p3indomain:cli's open queue; no p0,pm:blocking, opentarget:, p1 or p2 card is open for dispatch (#23497 ispm:blocked; #23050 is seat 2's). #23623:objectstack serve --no-server's ready banner stops printing anAPI:URL and an MCP connect URL when no HTTP server is composed, and says once that none is served · 2026-10-15T21:51ZSession:
session_01FLwgbTSmGMrqsXkBDsiwfGAccount:
os-bill(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23623-no-server-banner-urlsWorktree:
objectstack-issue-23623Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)File surface (read on
origin/mainfdbcec0b2e; stop on a breach and explain it in the report):packages/cli/src/utils/format.ts:printServerReady(:1058; theAPI:row:1083, theConsole:row:1084, theMCP:row and its connect hint:1087-:1090, the paths-only note:1091) and itsServerReadyOptions;packages/cli/src/commands/serve.ts: the banner thunk (:5376), the one production caller;packages/cli/src/**, and one changeset.objectstack:listeningIPC message orruntime.<environment>.<project>.json(publishBoundPort,:5484): those are machine-read channels with consumers of their own. What they publish on a--no-serverboot is read and reported, not changed.Container & model:
M(one production boot per shape at the public door, and the composed-server read a config-mounted transport can change),mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier --repo objectstack-ai/objectstackover the two paths atfdbcec0b2e: "no path-derived mandate … The tier stays the PM's per-card judgment call")Clause-②: no
Basis for the Clause-② line: printed operator text only.
utils/format.tsis not a published export of@objectstack/cli(itsexportsmap is.,./consoleand./hook-body), so a newServerReadyOptionsfield moves no public surface. No flag, exit code, accepted input, IPC message or state-file field moves, and there is nopackages/specedit.Responsibility:
packages/cli'sserveready banner, platform code in this lane | the same boot's diagnostics already sayRestApiPlugin: HTTP Server service http.server not found. REST routes skipped.| an operator, or an AI client being configured from the banner, on a--no-serverboot; measured in production by cli/serve: a productionobjectstack servewith no OS_DATABASE_URL registers no driver in silence, then fails at AuthPlugin.start with "No driver available for object 'sys_organization'" and a remedy naming a datasource nobody declared #23573's dev (os-dev-report6184721596, boot R10)Thread-read: 6186388871
Serial constraints cleared, and the landing order:
serve.tsorformat.ts(every open PR's file list was read in this act; the release PR touches onlypackages/cli/CHANGELOG.mdandpackage.jsonin this package), and nopm:dispatchedclaim names them. This seat has no other card in flight (serial).objectstack servewith no OS_DATABASE_URL registers no driver in silence, then fails at AuthPlugin.start with "No driver available for object 'sys_organization'" and a remedy naming a datasource nobody declared #23573, seat 2) landed onserve.tsat 08:54Z, before this claim.Restart-touch:line on an openpm:on-holdcard names these files (all 13 cards' bodies and comments were read in this act).Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23623,
"status": "done",
"branch": "claude/issue-23623-no-server-banner-urls",
"pr": "#23741",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG — this run's id (Claude-Session line on every commit; subagent of the domain:cli seat 1 PM session, same id)",
"premise_still_valid": true,
"summary": "printServerReady now takes a REQUIRED httpServer: 'composed' | 'skipped-by-no-server' | 'not-composed' and prints the address rows (API:, Console:, MCP: and its connect hint, the paths-only note) only for 'composed'; otherwise it prints none of them and one line, '➜ No HTTP server: --no-server was given and no plugin composed one, so no API or MCP address is served', naming --no-server only for 'skipped-by-no-server'. serve.ts reads the answer off the booted kernel through readComposedHttpServer (the http.server read resolveBoundPort already did, extracted and shared) via resolveHttpServerComposition(kernel, flags.server), so a config that mounts its own HonoServerPlugin under --no-server still prints its rows; the IPC message and runtime state file are untouched. Hypotheses measured on 9bb9370: H1 holds (API: unconditional, MCP: on mcpEnabled alone, serve passes isMcpServerEnabled()); H2 holds (resolveBoundPort reads http.server and getService throws when absent; its number cannot itself answer 'composed' because it falls back to the requested port, so the read was extracted rather than duplicated); H3 holds on callers (one production caller) but the field was made required, so 13 existing pins gained one line (see deviations). Draft PR #23741 is open, assignee os-bill.",
"tests": "All on 30b5c24 after
pnpm turbo run build --filter=@objectstack/cli(61 tasks, exit 0). New unit pins src/utils/format.server-ready-no-http-server.test.ts (9 cases: composed shape; skipped-by-no-server with mcpEnabled/ui/consolePath gives no address row, no http:// anywhere, one line naming --no-server; not-composed gives one line not naming it; paths-only note dropped; banner-minus-address-rows equals no-server-banner-minus-line, healthy and DEGRADED) and src/commands/serve-http-server-composition.test.ts (6 cases). New integration pin src/commands/serve-no-server-banner.integration.test.ts: 4 boots of bin/run.js (production --no-server, --dev --no-server, default control with health 200 on the API: port, config-mounted HonoServerPlugin under --no-server control) — 'Tests 4 passed (4)', 16.09 s and 16.46 s, test-budget 30. The 17 new+touched banner files: 'Test Files 17 passed (17) · Tests 202 passed (202)'. Unit layerpnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 --shard=K/4, K=1..4: 75/74/74/74 files, 1369+1079+886+1043 = 4377 tests passed (one unsharded attempt hit my 590 s timeout before the 10-minute cap, no verdict, discarded). Integration scoped: the new pin + test/serve-no-storage-boot-guard, serve-host-config-metadata-door, serve-view-container-read, serve-migrate-exit-schema-parity: 'Test Files 5 passed (5) · Tests 22 passed (22)'; the rest of the integration tier is declared to CI. Typecheckpnpm --filter @objectstack/cli typecheckexit 0 (tsc --noEmit + 'check:test-typecheck: OK'); --listFilesOnly counts the 3 new files in the program. Ablation (fix committed first; node scripts/ablation-replace.mjs wrap mode): the API: row moved back aboveif (opts.httpServer === 'composed')with marker 'ablation-23623', anchor x1 to x0, blob be095f8d3d42 to f3a63ad76d42; CLI rebuilt; ablation-dist-preflight: marker present in 1 built file; the unit + integration pins: 'Tests 9 failed | 4 passed (13)' (7 no-server unit cases and both no-server boots red on 'an address row printed'; composed cases and both boot controls green — direction: turned red, as predicted). Restore: blob be095f8d3d42 = HEAD, git diff HEAD empty, git status --porcelain empty; rebuilt; preflight --absent: absent from all 636 built files; 'Tests 13 passed (13)'.","gates": "Local union on 30b5c24:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; stderr: tree objectstack-ai/objectstack at 30b5c24, 19 paths vs merge base 9bb9370) derived 66 commands; each run ascmd > log 2>&1; ec=$?: 66 exit 0.pnpm check:dual-build-cjs-loadsandpnpm check:i18n-coveragefirst exited 3 (PREREQUISITE NOT MET: no dist/ for studio, client-react, connector-slack, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis, service-knowledge, skills); afterpnpm turbo run buildof those plus @objectstack/example-showcase... (72 tasks, exit 0) both re-ran exit 0 ('check-i18n-coverage: OK (14 config(s), 621 baselined untranslated string(s), none new)').--ran ran.list: '✓ dispatch-gates --ran: 66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED'. Lint lane blind spot:pnpm exec eslint packages/cli --no-inline-configexit 0, no output;--format jsonover the 5 changed code files: 0 messages; --print-config resolves 6 and 5 active rules. Not local by rule: check:dev-prereqs / check:skill-frame-freshness (no packages/spec build or frame file touched); repo-wide pnpm lint is CI's. PR #23741 check-runs read once at open (head 30b5c24): 23 runs; 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 21 in_progress (Lint · ESLint, Lint · repo & tooling gates, Lint · PM dispatch-gates, Type Check · workspace, Governed Surface Queue Guard, Check Changeset, Check PR Size, The card this PR closes must claim this branch, …) — NOT MEASURED: CI convergence is the PM's.","mcp_calls": "0",
"api_writes": "3 — via the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft, PR #23741) and POST /repos//issues/23741/assignees (os-bill), both legs of one pr_create action (run 38822569782, body read back byte-identical, 10654 bytes); POST /repos//issues/23623/comments (this os-dev-report). git push x5 (empty-branch probe + 4 commits) is not REST. No label-write, no PR-body PATCH.",
"open_questions": [
{
"question": "The seat's Zone 1 reading says a config-mounted HonoServerPlugin under --no-server makes 'the URLs true'. Measured: the API: origin is live (a REST data route answers 401) but /api/v1/mcp answers 404, because the dispatcher that mounts it is composed only under flags.server. This PR follows the ruling (the MCP: row prints there) and pins only the API: row in that control. Should the MCP: row also require the MCP route to be mounted?",
"options": [
"A — a follow-up card: gate the MCP: row on the dispatcher (the route's owner) being composed, read off the kernel like httpServer. Real need: zero in-repo producers of 'os serve config with its own HonoServerPlugin + --no-server' (the two docs mounting HonoServerPlugin are programmatic kernels); long-term: the banner advertises only what is mounted (AGENTS 'Machine-readable surfaces must not lie' spirit); AI-proofing: an AI client configured from the banner would never be sent to a 404; startup focus: S, but a second kernel read for a shape nobody runs.",
"B — leave it as ruled and keep the finding on record until a producer of the shape appears."
],
"recommendation": "B, because the shape has zero measured producers (startup-focus axis), the ruling explicitly keeps the row, and the finding below records the measured 404 with its reach so A can be dispatched the day a producer exists; A is the right shape if one does."
}
],
"out_of_scope_findings": [
"class: b · reach: public door measured —
os serve objectstack.config.ts --no-server --preset minimal(production, host fixture, spawned with an ipc channel, 30b5c24 and base 9bb9370) received {type:'objectstack:listening', port:35809, url:'http://localhost:35809'} and a GET to that port answered ECONNREFUSED; --dev --no-server the same · contract: content/docs/deployment/cli.mdx:269 'objectstack:listening|os serve| The HTTP server is bound. Carries{ port, url }— the port actually bound' · Seam: docs:content/docs/deployment/cli.mdx objectstack:listening row → runtime:packages/cli/src/commands/serve.ts publishBoundPort → runtimeBoundPortChannels.announceListening; consumer: os dev parent (dev.ts:707), which never spawns a --no-server child (os dev declares no server flag), so no in-repo consumer is reached · ruled out of this card (IPC not to be changed) · dedupe words: objectstack:listening no-server; IPC listening message without HTTP server; serve --no-server announces port","class: a · reach: public door measured —
os serve objectstack.config.ts --no-server --preset minimal(production) with a config that mounts its ownnew HonoServerPlugin({ port }): banner prints 'MCP: http://localhost:PORT/api/v1/mcp' and its connect hint; GET /api/v1/mcp answers 404 (also /api/v1/health 404; /api/v1/data/fx_task 401), on base 9bb9370 and 30b5c24 — the dispatcher that mounts /mcp is composed only under flags.server (serve.ts,if (flags.server)block after the REST plugin) · zero in-repo producers of the shape · see open_questions[0] · dedupe words: no-server config HonoServerPlugin MCP 404; banner MCP row unmounted route; mcpEnabled dispatcher not composed","class: a (user-visible misleading text) · reach: public door measured —
os serve objectstack.config.ts --no-server --preset minimal(production, host fixture, no OS_DATABASE_URL, no auth secret), no ipc channel: the banner's last line 'Press Ctrl+C to stop' printed 3134 ms after spawn and the process exited 0 at 3175 ms by itself (nothing holds the event loop); same with an ipc channel (exit 0 ~3.1-3.5 s, before any signal), on base 9bb9370 and 30b5c24;--dev --no-server(default preset) stays up. Contradicts the R10 reading in os-dev-report 6184721596 ('it stays up until SIGTERM'). Its runtime state file is deleted by that exit ~40 ms after being written · dedupe words: no-server preset minimal exits after banner; Press Ctrl+C to stop but process exited; kernel-only serve exits immediately"],
"line_budget": "n/a",
"deviations": [
"ServerReadyOptions.httpServer is REQUIRED, not the optional field with a today's-output default that H3 and the suggested route named: an omitted field defaulting to 'composed' re-creates 'absent means a server is listening' for the next caller; externalBaseOrigin in the same interface is required for the same reason. Cost: one
httpServer: 'composed'line in the base literal of 13 existing banner pins under packages/cli/src/** (tsc named 11; format.server-ready-dev-admin-audience and format.server-ready-app-logins castas ServerReadyOptionsand were found by reading); no assertion changed.","The real-boot pin lives at packages/cli/src/commands/serve-no-server-banner.integration.test.ts (the dispatch's file surface is packages/cli/src/), not packages/cli/test/. Consequence: it is outside check:cli-test-child-env's population (packages/cli/test/), so that gate's pinned built-entrypoint roster did not need (and did not get) an edit; the file strips TEST/VITEST/VITEST_/NODE_PATH and declares NODE_ENV: undefined itself, as src/commands/migrate/.integration.test.ts do.",
"Unit layer run as 4 shards (
--shard=K/4): one unsharded run could not finish inside the container's foreground cap.","The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer; per the dispatch and AGENTS.md, commits carry the model-free pair (the bare
Co-Authored-By: Claudenoreply trailer plusClaude-Session:) and the PR body ends with the session-URL footer form.","Two comments in serve.ts (BoundPortChannels.printBanner and the publish-order list at the banner site) said the banner's API: row always names the bound address; both now say 'when an HTTP server is composed'. Comment-only, inside the file surface."
],
"files_changed": [
"A .changeset/23623-no-server-banner-urls.md",
"M packages/cli/src/utils/format.ts",
"M packages/cli/src/commands/serve.ts",
"A packages/cli/src/utils/format.server-ready-no-http-server.test.ts",
"A packages/cli/src/commands/serve-http-server-composition.test.ts",
"A packages/cli/src/commands/serve-no-server-banner.integration.test.ts",
"M packages/cli/src/commands/dev-mcp-connect-hint-origin.test.ts",
"M packages/cli/src/commands/serve-automation-shadowing.test.ts",
"M packages/cli/src/commands/serve-banner-external-base-url.test.ts",
"M packages/cli/src/utils/format.boot-warning-classes.test.ts",
"M packages/cli/src/utils/format.config-artifact-row.test.ts",
"M packages/cli/src/utils/format.flow-contender-agreement.test.ts",
"M packages/cli/src/utils/format.seed-pending.test.ts",
"M packages/cli/src/utils/format.seed-summary.test.ts",
"M packages/cli/src/utils/format.server-ready-app-logins.test.ts",
"M packages/cli/src/utils/format.server-ready-base-url.test.ts",
"M packages/cli/src/utils/format.server-ready-degraded-boot.test.ts",
"M packages/cli/src/utils/format.server-ready-dev-admin-audience.test.ts",
"M packages/cli/src/utils/format.tenancy.test.ts",
"19 files, +697 / -27, vs merge base 9bb9370"
],
"before_after": {
"method": "node packages/cli/bin/run.js serve objectstack.config.ts --port PORT ARGS, built CLI, ipc channel, NODE_ENV unset, OS_CLOUD_URL=off, host fixture (one object, one plugin instance); before = 9bb9370, after = 30b5c24; probe = GET right after the banner, before SIGTERM",
"production --no-server --preset minimal": {
"before": [
"➜ API: http://localhost:42617/",
"➜ MCP: http://localhost:42617/api/v1/mcp",
"connect an AI client (Claude Code, Cursor, …) · skill: http://localhost:42617/api/v1/mcp/skill"
],
"after": [
"➜ No HTTP server: --no-server was given and no plugin composed one, so no API or MCP address is served"
],
"probe": "before and after: / , /api/v1/health, /api/v1/mcp all ECONNREFUSED",
"ready_line": "unchanged: '⚠ Server is ready — DEGRADED: missing core services: auth, job'"
},
"--dev --no-server": {
"before": [
"➜ API: http://localhost:33807/",
"➜ MCP: http://localhost:33807/api/v1/mcp",
"connect an AI client … · skill: http://localhost:33807/api/v1/mcp/skill"
],
"after": [
"➜ No HTTP server: --no-server was given and no plugin composed one, so no API or MCP address is served"
],
"probe": "before and after: all ECONNREFUSED",
"ready_line": "unchanged: '✓ Server is ready'"
},
"production default (OS_DATABASE_URL=file:./ctl.db, auth secret) — control": {
"before": [
"➜ API: http://localhost:44523/",
"➜ MCP: http://localhost:44523/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"after": [
"➜ API: http://localhost:41479/",
"➜ MCP: http://localhost:41479/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"probe": "before and after: / 302, /api/v1/health 200, /api/v1/mcp 401"
},
"production --no-server --preset minimal + config-mounted HonoServerPlugin — control": {
"before": [
"➜ API: http://localhost:41663/",
"➜ MCP: http://localhost:41663/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"after": [
"➜ API: http://localhost:35235/",
"➜ MCP: http://localhost:35235/api/v1/mcp",
"connect an AI client … · skill: …/api/v1/mcp/skill"
],
"probe": "before and after: / 404, /api/v1/health 404, /api/v1/mcp 404, /api/v1/data/fx_task 401 (the server listens; /mcp is not mounted — see findings)"
}
},
"channels": {
"objectstack:listening IPC (read, not changed)": "sent on every --no-server boot measured, before and after: production R10 shape {type:'objectstack:listening', port:35809, url:'http://localhost:35809'}; --dev --no-server {port:46531, url:'http://localhost:46531'} — the requested port (resolveBoundPort falls back when no transport is registered), nothing listening (ECONNREFUSED); followed by objectstack:seed-settled {ok:true, suppressed:[], sources:[]}",
"runtime.ENV.PROJECT.json (read, not changed)": "written on every --no-server boot measured, before and after: OS_HOME/runtime.env_local.plain-2a0a5271a3cf.json with {pid, port: the requested port, url: 'http://localhost:PORT', environmentId: 'env_local', startedAt}, present when the IPC message arrives; on the production --no-server --preset minimal shape it is removed ~40 ms later by the exit handler when the process exits on its own (see findings); on --dev --no-server it stays until stop. No docs contract found for this file (content/docs has none); serve.ts calls it a best-effort supervision file."
},
"pr_body_changes": [
{
"where": "## Acceptance notes, append after the last bullet",
"old": "- A comment in
start.tssaysprintServerReady"prints theAPI:row always". That is still true foros start, which never passes--no-server, so it is left as is.","new": "- A comment in
start.tssaysprintServerReady"prints theAPI:row always". That is still true foros start, which never passes--no-server, so it is left as is.\n-check:cli-test-child-envscanspackages/cli/test/**only, so the new boot pin, and the existingsrc/commands/migrate/*.integration.test.tsspawners, are outside its population. The new pin strips the vitest worker env and declaresNODE_ENV: undefineditself. Observation only; no carrier."}
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23741 →
2906cd8d2e(#23623). The card is closedcompleted, andpm:dispatchedis strippeddomain:cliseat 1 (#6024) ·os-bill· sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T23:57Z · holder of claim6203043845, released by this landing.origin/main2906cd8d2e: one parent (a queue squash), an ancestor oforigin/main, 19 files +697 / −27, withpackages/cliidentical to the reviewed head30b5c246e; no queue branch leftcompletedby the PR'sFixes #23623;pm:dispatchedremoved by targeted delete6204389833on a true-green head. No contract review was owed.os serve's ready banner printsAPI:,Console:,MCP:with its connect hint, and the paths-only note only when the booted kernel holds anhttp.server. Otherwise it prints one line saying no HTTP server is served, naming--no-serveronly when that flag is the reason.http.serverreadresolveBoundPortmakes, shared. A config that mounts its own transport under--no-serverkeeps its rows.ServerReadyOptions.httpServeris required, so a caller cannot get the address rows by omission.objectstack:listeningIPC message and the runtime state file still announcehttp://localhost:PORTwith nothing listening, and a config-mounted server's banner names an unmounted MCP route #23743: the remainder. Theobjectstack:listeningmessage and the runtime state file still announce an unbound port, and theMCP:row under a config-mounted server names an unmounted route.--no-server --preset minimalprints the ready banner ending "Press Ctrl+C to stop" and then exits 0 on its own about 40 ms later, removing the runtime state file it had just written #23744:--no-server --preset minimalprintsPress Ctrl+C to stop, then exits 0 on its own.packages/cli/src/commands/serve.tsandpackages/cli/src/utils/format.ts.Generated by Claude Code