Repository navigation
os dev says ✓ Server is ready while a background seed continuation may still emit its error wall a minute later — nothing an app can observe says the boot has come to rest #17329
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 10, 2026 Triage: lands in
os dev's readiness signal (packages/cli);domain:cli;priority:p2.os devprints✓ Server is readywhile a background seed continuation may still emit its error wall a minute later ⇒ nothing an app can observe says the boot has come to rest.⇒ p2: a readiness signal that is not a readiness signal. ⭐ The cost lands on exactly the consumers who do the right thing — a downstream app or test harness that waits for the documented ready line proceeds into a boot that is still failing, and the failure then looks like theirs.
⇒ Give the boot an observable settled state.
⚠️ ⛔ Do not fix it by delaying the ready line by a guessed interval — that trades a wrong signal for a slow wrong signal. The seed continuation either completes or fails; the signal should follow it.⚠️ Distinct from #17177 and the card says so — that one is the loader's summary contradicting the database, this one is timing. ⛔ Do not fold. ⭐ But they share a boot path, so whoever takes either should read the other; a fix that makes the boot's completion observable may make #17177's contradiction easier to catch too.⭐ Filed at the destination per hotclm's
AGENTS.md— report, never patch.Size/model suggestion:M.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T15:00Z · 本评论来自分诊座位
Generated by Claude Code
Claim: #17329 by the
domain:cliexecution PM seat (pm:seat#6024), sessionsession_01TSf4DV7ziu4V5j73e46b7c, R73, 2026-09-11T13:07Z.
Branch: claude/issue-17329-dev-ready-settled-signal
Thread-read: body + 1 comment, read to the last one (5620778727, os-litant, 2026-09-10T15:00:51Z) through REST, 2026-09-11T13:07Z.
Clause-②: yesDeclared file surface (⛔ the dev declares any addition in the PR under
## Scope):packages/cli/src/commands/dev.tsand whatever the readiness banner is actually emitted from, plus its sibling tests and a.changeset/. Read-only, ⛔ not to be edited: the seeder and seed-loader modules — this card is about the signal, not the seeding.Why the axis verdict above is
yes. Every shape the card names — an exit-coded wait mode, a line on a side channel, a health field that flips after seeding, or making the existing banner mean what it says — either adds published CLI surface or changes what an already-published line asserts. ⇒ graded the conservative way, soneeds:contract-reviewis owed and the PR does not enqueue until an at-tier review exists. ⛔ Never the other direction on a published command's observable contract.Serial-resource readings at claim time. Heavy-verify lock:
lock is free,queue: empty⇒ arrival depth 1, underLOCK_DEPTH_HOLD(2). Hot-file matrix re-derived from all 11 open PRs at 13:01Z:packages/cli/src/commands/dev.tsis FREE — the only open PR holding lane files is #17454, and it holdscommands/init.ts,commands/migrate/{account-issuer,apply}.ts,packages/cli/src/index.ts,packages/client/src/index.tsand onepackages/qa/dogfoodtest, none of them this surface.⚠️ packages/cli/src/index.tsis HARD SERIAL behind #17454's merge — if this fix needs to touch it, that is a wait, ⛔ not a race.PM dispatch seat ·
domain:cli· R73 · 2026-09-11T13:07Z
Generated by Claude Code
PREMISE 0, re-driven on this tree — and the fork
Dispatched as
os-devfor #17329 on branchclaude/issue-17329-dev-ready-settled-signal.
Delivered diff is empty by design. The remaining question is the platform's, and the card
says so: "That is the platform's call." Everything below is measurement, with controls.1. Where the two sites actually are on this tree
Located from the SYMBOL, not from the card's line numbers. The declared file surface in the
claim comment waspackages/cli/src/commands/dev.ts; that is not where the banner is emitted,
and this reading supersedes it.thing site on this tree readiness banner printServerReady—packages/cli/src/utils/format.ts:865; the string✓ Server is readyat:887,Press Ctrl+C to stopat:983banner call site packages/cli/src/commands/serve.ts:4802(a thunk), fired bypublishBoundPort(...)atserve.ts:4890— i.e. AFTERruntime.start()resolvesos dev's rolepackages/cli/src/commands/dev.ts:510only spawnsserve --devwithstdio: ['inherit','inherit','inherit','ipc']. It emits no banner and never callsprocess.senditselfbackground-seed decision packages/runtime/src/app-plugin.ts:1561-1572—Promise.race([seedPromise, budget]), budgetOS_INLINE_SEED_BUDGET_MS(default 8000)continuation's output success ctx.logger.info('[Seeder] Seed loading complete', ...)atapp-plugin.ts:1464; the loud path atapp-plugin.ts:1484-1496(Seed loading completed with N dropped record(s) and N error(s), then per-error lines)2. The premise HOLDS, and the inverted order is reproduced here
Eight live
os dev --freshboots ofexamples/app-showcaseon this tree. The decisive run
carried a temporary probe reading the kernel at the exact instantpublishBoundPortfires
(mutation proven on disk, proven to reachdist/, trap-restored, restore proven by blob hash
and emptygit diff HEAD):14:40:57.723 WARN [Seeder] Inline seed exceeded 1ms budget for com.example.showcase; continuing in background to avoid blocking kernel start. [PROBE] seed-settlement at banner time: {"pending":1,"inFlight":1,"suppressed":[]} ✓ Server is ready 14:41:00.326 INFO [Seeder] Seed loading complete {"inserted":132,...}✓ Server is readyprinted while the kernel's own settlement tally saidinFlight: 1, and the
continuation's output landed after it. The card's defect is present on this tree.Two further platform-emitted positive controls, from an error-wall run
(OS_INLINE_SEED_BUDGET_MS=1, a 3000-record corpus of unresolvable references):[platform-objects] fresh-datastore attestation deferred at kernel:ready: 1 seed source(s) still writing— the platform's OWN instrument saying the boot passes a completion milestone unsettled;HTTP server started successfully {"port":41738}and✅ Bootstrap completeat14:21:18.959, then[SeedLoader] Pass 2: resolving deferred references {"count":3000}, then a 5.6-second gap, then a 3000-error wall, and only then the banner. The server was accepting requests 5.8 seconds before the banner.
⚠️ Bound on the measurement, stated rather than smoothed over. Which side wins is a photo
finish, not a guarantee. In 7 other runs the continuation landed BEFORE the banner, because on
this container'ssqlite/sqlite-wasmdrivers the continuation is loop-blocking and starves
the banner path. Scaling the corpus does not decide it — both sides grow together (132 → 20132
rows; a continuation stretched to 9.4s still lost). The ordering is uncoupled by construction;
which clock wins depends on whether the continuation yields. That is the same fact the card
reports from the other side when two containers disagreed.Not measured: the suppressed-source branch (
multi-tenant-replay).OS_TENANCY_POSTURE=isolated
refuses to boot withoutOS_PLATFORM_OWNER_EMAIL, unrelated to this card. NOT MEASURED, not a zero.3. ⭐ The reading that changes what the fork IS
The answer to "has seeding settled?" already exists, is already published, and the CLI never asks for it.
reading value declared packages/spec/src/contracts/seed-settlement.ts:110—export const SEED_SETTLEMENT_SERVICE = 'seed-settlement' as constpublished subpath packages/spec/src/contracts/index.ts:76, reachable as@objectstack/spec/contractsin the published API surface packages/spec/api-surface/contracts.json:258carriesSEED_SETTLEMENT_SERVICE (const)registrar packages/runtime/src/seed-settlement.ts:130-131—kernel.registerService(SEED_SETTLEMENT_SERVICE, tracker). The runtime the CLI itself bootsreader packages/platform-objects/src/plugin.ts:231packages/clineither registers nor reads it — zero occurrences What it exposes, read from the contract rather than assumed: one synchronous method,
snapshot(): SeedSettlementSnapshotreturning{ pending, inFlight, suppressed[] }. A count,
not a promise and not an event — so a consumer polls or hooksapp:seeded, it cannot await it.Reachability at the right moment: MEASURED, not inferred. The probe above resolved the
service off the CLI's ownkernelhandle atpublishBoundPorttime and got a real snapshot back.
Positive control on the same path: the sibling reads a few lines above it in the same function —
kernel.getService('seed-summary')andkernel.getService('auth')— return real values in the
same boots (the banner printsSeeds: com.example.showcase 132 rowsand the dev-admin line from them).⇒ So the question put to the maintainer is not "invent a way to know". It is
"the knowing already exists and is published — decide how the CLI should TELL a parent process".⚠️ This does not dissolve the fork. Knowing and telling are two problems. The contract answers
the first. The second is still a published-surface decision, and still yours.4. The shapes, with costs. ⛔ Not choosing one.
The card's four candidates map 1:1 onto the four hard stops in my dispatch, so all four are yours:
# shape what it costs / decides A exit-coded wait mode ( --wait-for-seed)new published CLI flag + a new exit-code vocabulary. Widest blast radius; also the only shape a shell script can consume with no code B hold ✓ Server is readyuntil seeding settlesredefines what an already-published line asserts. Note it is NOT the guessed interval triage forbids — it follows the real settle. Real cost: the HTTP server is already accepting requests (measured: 5.8s early), so the operator stares at a blank terminal while a working server serves C health/API field that flips after settle new API field; puts an authenticated dependency into a demo script, which the card already rejects as a consumer workaround D new side channel that becomes contract two sub-shapes. D1: a second IPC message beside objectstack:listening(serve.ts:452) — but the consumer spawnsos dev, notserve, soos devmust re-emit on ITS ipc channel, which makes an ipc channel mandatory on a published command. D2: aseedfield inruntime.(environment).(project).json— butruntimeBoundPortChannelsdocuments that file as best-effort supervision, and #15374 already ruled against growing its payload into a contractE a settled line on the EXISTING stderr banner stream decides none of A–D. But it is reader-only: a parent with stdio: 'inherit'cannot read it, so it does not satisfy the card's stated requirement. It also grows published banner output on every boot, against this banner's own restraint ruleF banner's Seeds:row says "still writing" wheninFlight > 0same class as E — makes the transcript honest, gives a parent nothing. Would close the card's ⚠️ half ("a reader who seesPress Ctrl+C to stopreasonably believes nothing more is coming") without closing the cardRecommendation, offered as a recommendation only: D1. The production half already exists and is
published (section 3), theos devparent already holds an ipc channel toserveand already
consumes one message type on it, and thepublishBoundPortordering contract already owns "publish
the boot's facts in a safe order" — a settle message is the same shape one beat later. It costs the
least new vocabulary of A–D and leaves✓ Server is readyasserting exactly what it asserts today.
Its honest cost is the outward half:os devre-emitting to ITS parent makes an ipc channel part of
a published command's contract, and that is the decision I am not making.Second choice: B, on the argument that a readiness line which is not a readiness line is the
actual defect and renaming the problem is cheaper than adding a channel. I rank it below D1 only
because the measurement shows the server is genuinely serving well before the banner, so holding the
line makes a true statement later rather than a false one sooner.⛔ E and F are listed for completeness. Neither satisfies "a signal a parent process can wait on
without owning the child's stdout", so neither is a way to deliver code and call this card closed.5. Verification
Empty diff, so
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
derives 0 gate families (exit 2, "this branch changes nothing againstorigin/main").
Reconciled with--ran: derived 0 / accounted-for 0 / unrun 0.This lane's always-on full lint union ran anyway, under
scripts/pm/os-verify-lock.sh:pnpm eslint . --no-inline-config— exit 0 (captured before any pipe), 0 errors, 0 warnings- 6633 files linted, counted from eslint's own
--format jsonoutput - positive control for that zero:
packages/cli/src/utils/format.ts,packages/cli/src/commands/dev.ts,packages/cli/src/commands/serve.ts,packages/runtime/src/app-plugin.tsandpackages/spec/src/contracts/seed-settlement.tsall appear in eslint's own output
One
exit 99queue-timeout was taken on the shared lock and is recorded NOT MEASURED, neither pass
nor failure; the slot was resumed and the run completed green.6. Scope, and why there is no PR
Declared surface was
packages/cli/src/commands/dev.ts, its sibling tests and a.changeset/.
Delivered: nothing. The branch is pushed and carries zero commits. GitHub cannot open a pull
request with no commits between base and head, so there is no PR to link — the report lives here.
Say the word if you want the measurement carried as a docs commit so it has a PR; I did not add one
on my own, because an unrequested file is surface I was not asked to introduce.Changeset disposition: skip-changeset — nothing in any package's
files[]moves, because
nothing moves at all.Clause-②: no
That verdict is re-judged from the DELIVERED diff, which is empty: an empty diff changes no
published contract, so the axis is negative andneeds:contract-reviewis not owed. The claim
comment graded the axis affirmative on the conservative assumption that a shape would be delivered,
which was the right call at claim time and is simply not what happened. Re-grade if you disagree.
Generated by Claude Code
Addendum to the measurement — the over-budget banner is indistinguishable from a boot that seeded nothing
One more reading from the same instrumented run, because it changes how the reader-facing shapes
(E and F above) should be priced.printSeedSummary'sSeeds:row is fed bykernel.getService('seed-summary'), and that service is
populated byrecordSeedOutcome(...)— which the seeder calls only when the load finishes
(packages/runtime/src/app-plugin.ts:1453). On the over-budget path the load has not finished when
the banner prints, so there is nothing to read.Measured, with a positive control on the same path:
run seed state at banner Seeds:rows in the transcriptP1 — OS_INLINE_SEED_BUDGET_MS=1, probe read{"pending":1,"inFlight":1}at banner timestill writing 0 A — same app, seed settled before the banner (positive control) settled 1 ( Seeds: com.example.showcase 132 rows)So on the over-budget boot the banner is not merely early. Its seed line is absent, and the
transcript a reader gets is byte-indistinguishable from an app that declared no seeds at all:✓ Server is ready ➜ API: http://localhost:41741/ ... Flows: 30 flow(s) 20 bound to triggers ... Press Ctrl+C to stop⇒ This is the card's
⚠️ half with a mechanism attached: "a reader who seesPress Ctrl+C to stop
reasonably believes nothing more is coming." They believe it because the banner in front of them
says, by omission, that seeding is not part of this boot. The louder the seed goes on to fail, the
more complete the omission looks at the moment it is read.⛔ Not filed separately and ⛔ not folded into #17177: it is the same timing defect this card is
about, seen on the banner surface rather than on the clock. Recording it here so whoever takes the
shape decision prices E and F against what the banner actually prints today, not against the
settled-boot transcript.
Generated by Claude Code
os-dev-report
{ "issue": 17329, "status": "needs_decision", "branch": "claude/issue-17329-dev-ready-settled-signal", "pr": null, "premise_still_valid": true, "summary": "PREMISE 0 re-driven on this tree and CONFIRMED, with the inverted order reproduced here rather than inherited from the card. Emission sites relocated from the SYMBOL: the banner is printServerReady in packages/cli/src/utils/format.ts:865 ('Server is ready' at :887, 'Press Ctrl+C to stop' at :983), called from serve.ts:4802 and fired by publishBoundPort(...) at serve.ts:4890 after runtime.start() resolves - NOT from commands/dev.ts, which only spawns 'serve --dev' over stdio inherit + ipc. The background-seed decision is app-plugin.ts:1561-1572. Decisive run (temporary probe at the publishBoundPort call site): 'Server is ready' printed while the kernel's own tally read {pending:1,inFlight:1,suppressed:[]}, and '[Seeder] Seed loading complete' landed AFTER it. Second measured fact, posted as an addendum: on the over-budget path the banner's 'Seeds:' row is ABSENT (0 occurrences vs 1 in the settled control), so the transcript is indistinguishable from a boot that declared no seeds. THE READING THAT CHANGES THE FORK: the answer to 'has seeding settled?' is already produced and already published - SEED_SETTLEMENT_SERVICE is declared at packages/spec/src/contracts/seed-settlement.ts:110, exported on @objectstack/spec/contracts, carried in packages/spec/api-surface/contracts.json:258, and REGISTERED by @objectstack/runtime (seed-settlement.ts:130-131, the runtime the CLI itself boots); packages/cli neither registers nor reads it. So the maintainer is not being asked to invent a way to KNOW - only to decide how the CLI should TELL a parent process. That second half is still a published-surface decision and I did not make it. Delivered diff is EMPTY; the fork is enumerated with costs in issue comment 5636189867 and the addendum in 5636206628.", "clause_2_declaration": "Clause-②: no - re-judged from the DELIVERED diff, which is empty. An empty diff changes no published contract, so the axis is negative and needs:contract-review is not owed and was not applied. The claim comment graded the axis affirmative on the conservative assumption that a shape would be delivered; that was correct at claim time and is simply not what happened. PM may re-grade.", "tests": "GATE ROSTER: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 0 families - exit 2, 'this branch changes nothing against origin/main (merge base 49cd71548)'. Reconciled with --ran: derived 0 / accounted-for 0 / unrun 0. Exit 2 here means 'nothing to derive', not a failure. LINT UNION (this lane's always-on addition, which dispatch-gates does not name), run under scripts/pm/os-verify-lock.sh: `pnpm eslint . --no-inline-config` -> exit 0, captured with `cmd > file 2>&1; EXIT=$?` BEFORE any pipe; 0 errors, 0 warnings; 6633 files counted from eslint's own --format json output; positive control for that zero - packages/cli/src/utils/format.ts, packages/cli/src/commands/dev.ts, packages/cli/src/commands/serve.ts, packages/runtime/src/app-plugin.ts and packages/spec/src/contracts/seed-settlement.ts all appear in eslint's own output. No package tests or typechecks are owed: the diff is empty, so no package is affected. MEASUREMENT RUNS (8 live `os dev --fresh` boots of examples/app-showcase, none of them a gate): each preceded by a full dependency-closure build under the lock. Two one-off mutations, both ablation-shaped with a trap on EXIT INT TERM running the restore: (1) a widened seed corpus in examples/app-showcase, (2) a probe at the publishBoundPort call site in packages/cli/src/commands/serve.ts. BOTH legs proven, not assumed - mutation landed on disk by grep -c against the anchor text (0 -> 2/3 occurrences), reached dist/ by grep -c on the rebuilt artifact (20000 seed records in objectstack.json; 2 probe occurrences in dist/commands/serve.js), and RESTORED with proof: `git checkout HEAD -- PATH` (PATH spelled out, never a bare dash-dash-path) (never bare), then `git hash-object` == the HEAD blob hash (3a24a642... and f48928e2...) AND `git diff HEAD` empty, plus a rebuild confirming 0 probe occurrences left in dist/. Working tree verified clean at report time (`git status --porcelain` empty). One probe attempt aborted on its own landing guard (my threshold was 3, the probe text carries 2) - it restored cleanly and I corrected the threshold rather than re-running until something stuck; recording it because the first attempt was a no-op, not a reading. LOCK: one `exit 99` queue-timeout taken and recorded NOT MEASURED - neither pass nor failure; slot 'dev-17329' resumed and the run completed green. NOT MEASURED: the suppressed-source branch (multi-tenant-replay). OS_TENANCY_POSTURE=isolated refuses to boot without OS_PLATFORM_OWNER_EMAIL, unrelated to this card - a refusal, not a zero. BOUND ON THE MEASUREMENT, stated rather than smoothed: which side wins is a photo finish. In 7 other runs the continuation landed BEFORE the banner, because on this container's sqlite / sqlite-wasm drivers it is loop-blocking and starves the banner path. Scaling the corpus does not decide it - both sides grow together (132 -> 20132 rows; a continuation stretched to 9.4s still lost). The ordering is uncoupled by construction; which clock wins depends on whether the continuation yields. I did NOT reproduce the card's 82-second gap, and do not claim to.", "changeset_level": "skip-changeset - nothing in any package's files[] moves, because nothing moves at all. If the PM wants the measurement carried as a docs commit so a PR can exist, that commit would publish nothing either and the disposition would not change.", "mcp_calls": "0 - every GitHub read and write went over REST (repo-scoped probe returned 200 at the top of the run). Card body, its triage comment, the claim comment and #17177 were all read over REST; both issue comments were posted over REST and read back in full.", "open_questions": [ { "question": "THE FORK. All four shapes the card names map 1:1 onto the four hard stops, so the shape decision is the platform's. Which shape should give the boot an observable settled state? Note the newly measured fact that reframes it: the KNOWING already exists and is published (SEED_SETTLEMENT_SERVICE, registered by the runtime the CLI boots, never read by the CLI). Only the TELLING is undecided.", "options": [ "A - exit-coded wait mode (--wait-for-seed): new published CLI flag plus a new exit-code vocabulary. Widest blast radius, but the only shape a shell script consumes with no code.", "B - hold the ready line until seeding settles: redefines what an already-published line asserts. NOT the guessed interval triage forbids, since it follows the real settle. Real cost: the HTTP server is already accepting requests (measured 5.8s early), so the operator stares at a blank terminal while a working server serves.", "C - health/API field that flips after settle: new API field, and puts an authenticated dependency into a demo script - which the card already rejects as a consumer workaround.", "D1 - a second IPC message beside objectstack:listening (serve.ts:452). Production half already exists and is published; os dev already holds an ipc channel to serve and already consumes one message type on it. Outward half is the cost: the consumer spawns os dev, not serve, so os dev must re-emit to ITS parent, which makes an ipc channel part of a published command's contract.", "D2 - a seed field in runtime.(environment).(project).json. runtimeBoundPortChannels documents that file as best-effort supervision, and #15374 already ruled against growing its payload into a contract.", "E - a settled line on the EXISTING stderr banner stream: decides none of A-D, but is reader-only - a parent with stdio inherit cannot read it, so it does not satisfy the card. Also grows published banner output on every boot, against this banner's own restraint rule.", "F - the banner's Seeds: row says 'still writing' when inFlight > 0: same class as E. Would close the card's warning half - and the addendum measurement shows that half is worse than the card knew, since today that row is ABSENT entirely on the over-budget path." ], "recommendation": "D1, offered as a recommendation only and NOT implemented. It costs the least new vocabulary of A-D, reuses a channel and an ordering contract (publishBoundPort) that already exist, and leaves the published ready line asserting exactly what it asserts today. Second choice B, on the argument that a readiness line which is not a readiness line is the actual defect; ranked below D1 only because the measurement shows the server is genuinely serving well before the banner, so holding the line makes a true statement later rather than a false one sooner. E and F are listed for completeness and are NOT a way to deliver code and call this card closed." }, { "question": "No PR exists. GitHub cannot open a pull request with zero commits between base and head, and the delivered diff is empty by design. The branch is pushed and sits at 49cd7154. Do you want the measurement carried as a docs commit purely so a PR can exist?", "options": [ "Leave it as is - the fork report lives in issue comments 5636189867 and 5636206628, which is where a reviewer reads it.", "Ask me to land a docs/ADR commit carrying the measurement so there is a reviewable PR." ], "recommendation": "Leave it as is. I did not add a file on my own initiative because an unrequested doc is surface I was not asked to introduce, and the report is already on the card." } ], "out_of_scope_findings": [ "noted, not filed: `os dev`'s `watch` flag is declared Flags.boolean({ default: true }) with no allowNo (dev.ts:129), so neither --no-watch nor --watch=false is accepted, while its siblings --[no-]restart and --[no-]seed-admin are - and the watch-recompile block's own comment names '--watch=false (user opted out)' as the opt-out spelling. Incompleteness plus a stale comment, not a failure: nothing user-facing documents a spelling that fails, so it is not category (a). Carrier: the next PR touching dev.ts flags.", "noted, not filed: `pnpm --filter 'PKG...' build --concurrency=2` forwards the flag into @objectstack/cli's build script, which is a raw `if ... then tsc ... fi` shell line, and it dies with `sh: 1: Syntax error: word unexpected`. This is the documented pnpm trap (the flag belongs before --filter and is spelled --workspace-concurrency), i.e. my invocation error rather than a repo defect. Recording it only because the failure text names the package and reads like a package problem. Carrier: none." ] }
Generated by Claude Code
4 remaining items
Deferred, not shelved — the ruled implementation is hard-serial-fenced on THREE files across two open PRs
domain:cliexecution seat (#6024), round 22, 2026-09-12T04:52Z. The director seat's ruling at5642795097makes this card work again, and it sorts into this lane's selection order ahead of every2026-09-11p2. It is not being dispatched this batch, and 「延后不是搁置,被延后那一刻就把已知的坑记到该卡上」 — so here is the pit.The fence, measured rather than assumed
The ruled shape (D1 + the banner rider) touches exactly the files two open PRs are holding. File faces read from
GET /pulls/<n>/filesat 2026-09-12T04:51Z:ruled work site on this tree held by status serveemits a second ipc message besideobjectstack:listeningpackages/cli/src/commands/serve.ts(publishBoundPort,:4890)#17725 ( serve.ts+102/−8, andserve-bound-port-publication.test.ts+58/−3 — the very test that pins the ipc publication)open, draft os devforwards it to its own parentpackages/cli/src/commands/dev.ts(:510, theserve --devspawn with theipcchannel)#17725 ( dev.ts+46/−3)open, draft rider 2: the over-budget banner says seeding continues, and the Seeds:row says pendingpackages/cli/src/utils/format.ts(printServerReady:865, the banner string:887)#17775 ( format.ts+82/−20)open, draft ⇒ three of three. ⛔ Not a package-level or check-name overlap that a later landing could untangle —
serve.ts's collision is on the same function, andformat.ts's is on the same printer.Release condition: the MERGE of both PRs, ⛔ not the arm. A PR flipped ready, enqueued, or approved releases nothing; the file is free when the squash lands on
main. #17725 additionally carries a liveneeds:contract-reviewgate and an open scope question with the maintainer, so its merge is not near.What the next dispatcher must carry forward
- ⭐ The declared file surface in this seat's earlier claim (
5634883930) was WRONG and is superseded. It namedpackages/cli/src/commands/dev.ts; the dev relocated every site from the symbol and found the banner is emitted frompackages/cli/src/utils/format.tsand fired fromserve.ts—dev.tsonly spawns. The relocation reading is5636189867. ⛔ Do not re-derive the surface from the old claim. - The producer half already exists and is published —
SEED_SETTLEMENT_SERVICE(packages/spec/src/contracts/seed-settlement.ts:110, inapi-surface/contracts.json), registered by the runtime the CLI boots, andemitSeedSettled(true)already hangs off the detached promise atpackages/runtime/src/app-plugin.ts:1566-:1571. ⛔ Readingpackages/specis fine; editing it is another seat's card — the ruled work is the hop outward, and nothing in it requires a spec change. Clause-②: yesis already ruled on the card, soneeds:contract-reviewis owed at dispatch and the PR does not enqueue without an at-tier review.- ⛔ The two prohibitions on the card still stand: never delay the ready line by a guessed interval (the signal follows the real settlement), and never fold The seed loader logs
owner_id stays NULLfor 120 rows whoseowner_idis not NULL — the summary contradicts the table it just wrote #17177 into this.
This card stays
pm:queuewith no assignee — it is genuinely available, just not to a batch that would have to wait on two merges to write its first line. The lane dispatches the next unfenced card by the same order and comes back to this one whenmaincarries both landings.
Generated by Claude Code
- ⭐ The declared file surface in this seat's earlier claim (
Fence update — one of the three released, the card is still fenced.
domain:cliseat (#6024), 2026-09-12T05:41Z.⚠️ Corrected in place at 05:43Z: the first version of this comment lost three backticked fragments to a shell quoting fault on the way out — a lane name, a label name and this comment's own link label. The readings were never wrong, the transport ate them. Restored below.#17775 merged at 05:36:07Z as
56103b73a7f1f9063cd84ff02e106f5b1b69b56c⇒packages/cli/src/utils/format.ts(the banner printer, rider 2 of the ruling) is free. Note thatprintServerReadymoved in that landing:collectMetadataStatsabove it grew by ~80 lines, so ⛔ do not carry the:865/:887line numbers forward — locate from the symbol.Still held by #17725 (open, draft,
needs:contract-reviewlive, and an unanswered scope question with the maintainer as of this reading):packages/cli/src/commands/serve.ts— where the second ipc message has to be emitted, and itsserve-bound-port-publication.test.tsis in that diff too.packages/cli/src/commands/dev.ts— where the forward to the parent process has to go.
⇒ 2 of 3, and the two that remain are the ones the ruled D1 mechanism cannot be written without. Release is still the MERGE of #17725, ⛔ not its arm. Everything else recorded at
5643512620stands unchanged.
Generated by Claude Code
Claim: session_01TSf4DV7ziu4V5j73e46b7c
Branch: claude/issue-17329-seed-settled-ipc-message
Clause-②: yesDispatch —
domain:cliexecution seat (#6024), round 22, 2026-09-12T17:26Z, onorigin/mainat89a652b8. The assignee and thisClaim:line are written by the seat on the dev's behalf: the dev inherits both, ⛔ posts no second claim and ⛔ writes no assignee.The fence is RELEASED, and by the MERGE rather than the arm. The 04:52Z reading (
5643512620) found all three ruled files held. Both holders have since landed: #17775 merged 05:36:07Z (format.ts) and #17725 merged 2026-09-12T17:21:51Z as89a652b8(serve.ts,dev.ts, andserve-bound-port-publication.test.ts— the very test that pins the ipc publication). Re-measured at 17:30Z over all 20 open PRs, file-by-file: no open PR holds any path in this face, with the control lit in the same pass (20 of 20 returned a non-empty file list).
1. The ruling, quoted — this is settled, ⛔ not yours or mine to re-open
Recorded at
5642795097, director seat, decision batch #118 item 5, 2026-09-12, maintainer verbatim 「其他同意」 to D1:serveemits a second ipc message besideobjectstack:listeningwhen the seed settlement service reports settled (success or failure, with the summary), andos devforwards it to its own parent process when one holds the channel.Clause-②: yes— a published command's contract gains an ipc message; contract-review carrier.- Rider, same PR: on the over-budget path the banner prints a line saying seeding continues in the background (and the
Seeds:row says pending, not nothing), so the transcript is no longer byte-identical to a boot that declared no seeds. Human-facing only; it does not replace 1. - ⛔ Before landing, MEASURE the case the seat flagged: multi-tenant replay and
skipSeedDatareportpending > 0for the whole boot (seed-settlement.ts:44-52) — a consumer waiting on the new message must not hang forever there; the message's contract states what it means in those modes. - Docs: the
os devpage documents the two ipc messages and the pattern for a parent that spawnsos dev.
And the two shapes it refused, so you do not re-invent them:
⛔ B (hold the banner until seeding settles) not taken: the HTTP server is serving 5.8 s before the banner, and a shell script still cannot wait on prose.
⛔ A (--wait-for-seed+ exit-code vocabulary) not taken now; it remains the fallback if a no-code consumer turns out to need it.⭐ The producer half already exists and is published. The ruling's own words: 「what is missing is the one hop outward」.
SEED_SETTLEMENT_SERVICEis declared inpackages/spec/src/contracts/seed-settlement.tsand registered by the runtime the CLI itself boots;packages/runtime/src/app-plugin.tsalready hangs.then(() => emitSeedSettled(true))on the detached promise. ⇒ your job is the hop, ⛔ not a new settlement mechanism.2. ⛔ Locate every site from its SYMBOL — the line numbers in this thread are all stale, twice over
This is not a style note. The earlier claim on this card named the wrong file, and two landings have moved the rest today:
- ⛔ The claim at
5634883930declaredpackages/cli/src/commands/dev.tsas the banner's home. It is not. The relocation reading5636189867supersedes it: the banner isprintServerReadyinpackages/cli/src/utils/format.ts, called fromserve.tsand fired bypublishBoundPort(...);dev.tsonly spawnsserve --devoverstdio: inherit+ipc. - ⛔
format.ts's line numbers moved when fix(cli): os validate, os build and os info count the objects an option-B project declares, so --strict stops refusing a conforming stack #17775 landed (collectMetadataStatsaboveprintServerReadygrew ~80 lines) — the fence comment says so in as many words. - ⛔
serve.tsanddev.tsmoved again 30 minutes ago when feat(cli):objectstack dev --cert/--keyterminates TLS in the dev process, and the canonical origin follows the listener #17725 landed (serve.ts+102/−8,dev.ts+46/−3).
⇒ every coordinate in this thread is a timestamped reading, not an address. Re-derive each one and say so in your report.
3. Your file face
path what is expected packages/cli/src/commands/serve.tsthe second ipc message, emitted beside the existing objectstack:listeningannouncement when the settlement service reports settled — success or failure, carrying the summary.packages/cli/src/commands/dev.tsforward it to os dev's own parent when one holds the channel (the existing consumer is at themsg?.type === 'objectstack:listening'branch). ⛔ A parent that does not hold an ipc channel must be unaffected.packages/cli/src/utils/format.tsrider 2 — printServerReady's over-budget path says seeding continues, and theSeeds:row reads pending rather than vanishing.tests serve-bound-port-publication.test.tsalready pins the first message (the objectstack:listening IPC message really reaches process.send) — the second belongs beside it, plus whatever the forward and the banner need.content/docs/the os devpage: both ipc messages and the parent-spawn pattern (ruled item 4). ⛔ Nevercontent/docs/releases/..changeset/one changeset — see §5 for the level. ⛔ READ-ONLY, and one of them is held by another lane's open PR.
packages/runtime/src/app-plugin.ts(the seeder's detached-promise branch andemitSeedSettled) andpackages/runtime/src/seed-settlement.tsare to be read, never edited — this card is about the signal, not the seeding.⚠️ app-plugin.tsis additionally held right now by open draft #17718, which is transferred to thedomain:specseat. Touching it would collide with another seat's PR.⛔
packages/specis OUT of your face. 「凡触packages/spec一律转domain:spec座位,不论谁需要它」 —SKILL.md:231·core-rules.md:62·SKILL.md:287·lanes/cli.md:12·lanes/spec.md:12·dispatch-runbook.md:158.SEED_SETTLEMENT_SERVICElives there and you will need to read it; if the design needs to change it, stop and report.4. Ruled item 3 is an acceptance gate, not a footnote
「multi-tenant replay and
skipSeedDatareportpending > 0for the whole boot」 ⇒ a consumer waiting on the new message must not hang forever. Measure both modes on a real boot, then make the message's own contract say what it means there. ⛔ Do not ship a message whose absence is indistinguishable from a boot that will never send it. ⭐ The card's own history is the reason this is ruled: the over-budget banner was byte-identical to a boot that seeded nothing, and that is exactly how the defect hid.5.
Clause-②: yes, and the level that follows from itThe ruling declares it: 「
Clause-②: yes— a published command's contract gains an ipc message; contract-review carrier」. Both carriers are hung at this claim (card now, PR side the moment the PR exists).⚠️ Ayesand an all-patchchangeset is a redCheck Changesetby construction — the level axis requires at least one package whosepackages/**/src/**the diff moves to be gradedminoror above. A published command's ipc contract gaining a message is an additive widening ⇒@objectstack/cliatminor. ⛔ Never drop the changeset and ⛔ never re-declarenoto dodge the level; if your measurement genuinely says the surface did not move, say so with the reading and ⛔ leave the claim line alone — the seat corrects it at review.6. Gates
Derive them, ⛔ do not guess:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack. Capture each exit code before any pipe; read each verdict from the command's own printed line. Exit 3 isPREREQUISITE NOT MET— ⛔ neither a pass nor a finding.⚠️ This lane's blind spot:dispatch-gatesdoes not namepnpm lint. Always add it, as the full union (eslint . --no-inline-config, ⛔ no narrowing). Heavy runs throughscripts/pm/os-verify-lock.sh; quote the wrapper'sVERDICT command-exitline. Anything you could not run, name it rather than letting it read as green.7. Deliverable
Branch above, one changeset, a draft PR with
Fixes #17329, and a structured report. ⭐ An ablation is what buys the acceptance: break the hop and show the new pin go red, then restore and prove the restore by blob hash and a clean whole-treegit status.⚠️ Commit trailers: use the model-free pair. This repo's pre-pushcheck:commit-card-trailersmechanically refuses a trailer carrying a model identifier — measured on #15484. UseCo-Authored-By: Claude <noreply@anthropic.com>plusClaude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c, and ⛔ put no model identifier in the PR title, PR body, code comments or any other pushed artefact.⛔ Stop and report rather than racing if the design needs
packages/spec, if it needsapp-plugin.tsorseed-settlement.tsedited rather than read, if it needs a file this order did not name, or if ruled item 3's measurement says the message cannot honestly answer those two modes.
Generated by Claude Code
os-dev-report
{ "issue": 17329, "status": "done", "branch": "claude/issue-17329-seed-settled-ipc-message", "pr": "https://github.com/objectstack-ai/objectstack/pull/17892", "premise_still_valid": true, "summary": "Implements ruled D1 (batch #118 item 5) plus its banner rider. `os serve` announces a second ipc message, `objectstack:seed-settled`, beside `objectstack:listening` when this boot's seeding has come to rest — latched so it can never precede the listening message — and `os dev` relays it verbatim to its own parent when one holds the channel. The producer was untouched: `packages/runtime` and `packages/spec` were read only, exactly as ordered, and nothing in the design needed either changed; the CLI subscribes to two hooks the kernel already fires (`app:seeded`, `kernel:ready`) and reads the tally the `seed-settlement` contract already publishes. The message is keyed on `inFlight === 0`, NOT `pending === 0`: suppressed sources keep `pending` above zero for the life of the boot, so a `pending`-keyed message would never be sent there and its absence would be indistinguishable from a boot still writing — the card's own defect one level up. Rider 2: the over-budget banner now prints `Seeds: pending — N sources still writing` plus a line saying seeding continues in the background, and names suppressed sources instead, so the transcript is no longer byte-identical to an app that declares no seeds. Every site was re-derived from its SYMBOL on `origin/main` at 272c04b46; no line number from the thread was carried forward.", "tests": "All heavy runs serialised through scripts/pm/os-verify-lock.sh; every verdict read from the wrapper's own `VERDICT command-exit` line, never a bare $?. Measurements quoted at final HEAD 225ad588f. — RULED ITEM 3, MEASURED ON A REAL BOOT: `os dev` under OS_TENANCY_POSTURE=group with @objectstack/organizations declared by the host app, driven by a real ipc parent recording with the clock: banner at +9.954s, then `IPC {\"type\":\"objectstack:seed-settled\",\"ok\":true,\"suppressed\":[\"multi-tenant-replay\"],\"sources\":[]}` at +9.955s, with the banner reading `Seeds: not run this boot (multi-tenant-replay)`. pending never reaches zero on that boot and the consumer still does not hang. Reaching it needed three refusals satisfied in order (host-app declaration per #4719, OS_PLATFORM_OWNER_EMAIL, OS_AUTH_MEMBERSHIP_POLICY); the app-manifest declaration was temporary — package.json and pnpm-lock.yaml both restored to their HEAD blob hashes (d98f17aa…, 54c056aa…) with a clean whole-tree git status, and neither file is in the PR. skip-seed-data was NOT reached live and that is a code-path fact, not a gap: it is set only by createStandaloneStack on `os migrate`'s planning path, so no serve/dev boot can enter it; pinned structurally instead, with packages/runtime/src/app-plugin.seed.test.ts green in the same session as the producer-side control (14/14) pinning the exact snapshots this message consumes. — OTHER CLOCKS, MEASURED: in-budget banner +13.728s / settle +13.729s ok:true 132 rows; over-budget (OS_INLINE_SEED_BUDGET_MS=1) banner +8.938s / settle +8.940s, budget WARN fired; failure path (memory driver) banner +8.533s / settle +8.535s with ok:false and 110 ok / 22 errors. BOUND STATED: the still-writing-at-banner sub-case did NOT reproduce in 8 live attempts across sqlite, sqlite-wasm and memory — on this container the continuation completes during the remaining seconds of plugin startup — so it is driven as a unit with an ablation rather than waited for. — ABLATION, three legs, each with on-disk mutation proof (anchor uniqueness before; old/new grep counts and a moved blob hash after) BEFORE any verdict was read: (1) inFlight===0 -> pending===0 => 3 failed / 42 passed, both suppressed-mode pins plus the discriminating pin; (2) drop the latch's `released` gate => 1 failed / 44 passed, the ordering pin; (3) remove the kernel:ready hook => 1 failed / 44 passed, the wiring pin. Each restored with `git checkout HEAD -- PATH` (never bare --), restored blob 8fa02a659e8cae77207e0e326cbb51ad69e2ceeb matching HEAD on all three legs, clean whole-tree git status at the end. — GATES: 93 derived families from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (change set derived by the tool itself vs merge base 272c04b46, three-dot), all green. Two first returned a prerequisite rather than a verdict — check:skill-examples exit 1 and check:dual-build-cjs-loads exit 3 = PREREQUISITE NOT MET, both naming unbuilt packages — and were read as NEITHER pass nor finding; after building @objectstack/client-react and @objectstack/organizations both re-ran exit 0. — LINT, the lane's declared blind spot, run as the FULL union and not narrowed: `eslint . --no-inline-config` exit 0 captured before any pipe; 6672 files counted from eslint's own --format json; 0 errors, 0 warnings; positive control = all 6 changed source files present by name in eslint's own output. — TESTS: CLI unit tier 204 files / 2935 tests passed; CLI integration tier 45 files / 397 tests passed (run locally rather than declared to CI because this diff touches the kernel startup path); `pnpm --filter @objectstack/cli typecheck` exit 0 including check:test-typecheck; `pnpm --filter '@objectstack/cli^...' build` exit 0. 64 of those assertions are new across three files.", "mcp_calls": "0 — every GitHub read and write went through plain REST via the agent proxy; no MCP GitHub tool was called.", "open_questions": [ { "question": "`os dev` consumes `objectstack:listening` itself and relays only `objectstack:seed-settled`, so an ipc parent of `os dev` receives exactly one of the two messages (measured). Ruled item 4 asks the docs to describe 'the two ipc messages', which they now do — but should `os dev` also relay the listening message so a parent of `os dev` can see both without spawning `os serve` directly?", "options": [ "A — leave as shipped: `os dev` relays only the settle message; the docs state the asymmetry and point a consumer needing both at `os serve`.", "B — also relay `objectstack:listening` from `os dev` after it has consumed it, making the parent's view of the two commands identical.", "C — relay every `objectstack:*` message generically, so future child messages need no per-message hop." ], "recommendation": "A, because the ruling names exactly one hop ('os dev forwards IT') and B/C widen a published command's ipc contract further than was ruled — C especially, which would forward messages nobody has designed a parent contract for. The asymmetry is now documented rather than silent, which is what made it worth raising; if a real consumer needs the port from `os dev` it is a cheap follow-up card, not a reason to widen the surface unreviewed here." }, { "question": "`skip-seed-data` cannot be produced by any `os serve` / `os dev` boot (only `os migrate`'s standalone planning stack sets it), so the live half of ruled item 3 covers `multi-tenant-replay` only. Is the structural pin plus the green producer-side runtime pin sufficient evidence for that half?", "options": [ "A — accept as delivered: the branch is unreachable from the commands in this card's face, pinned against the contract's own SeedSettlementSnapshot, with the runtime's producer-side pin green as the control.", "B — hold the card until `skip-seed-data` is driven end-to-end through an `os migrate` planning boot, which publishes no ipc message and so would measure the snapshot rather than the hop.", "C — extend the announcer to `os migrate`'s standalone stack so the mode becomes reachable and measurable." ], "recommendation": "A. B would measure the producer, not this card's hop — `os migrate` opens no ipc channel, so there is no message to observe — and C is unruled scope in a command the order did not name. The honest statement is that the mode is unreachable here and answered correctly where it can occur." } ], "out_of_scope_findings": [ "noted, not filed: `os dev` relays `objectstack:seed-settled` but consumes `objectstack:listening`, so an ipc parent of `os dev` sees one message where `os serve` sends two — measured, and now stated in the docs and the PR's acceptance notes rather than left silent. Not a defect against any declared contract (nothing ever promised `os dev` relays the listening message), so it fails the (b) test for filing; carried as an open question instead. Successor: none — no open PR or queued card touches this surface, and the docs change means the next author meets the fact rather than rediscovering it.", "noted, not filed: reaching a genuinely walled boot needs three separate refusals satisfied in order (host-app declaration of @objectstack/organizations per #4719, OS_PLATFORM_OWNER_EMAIL, OS_AUTH_MEMBERSHIP_POLICY), each discovered only by hitting it — every refusal was clear and actionable, so this is an observation about a multi-step setup path and not a defect, a contract violation, or an authoring trap. Successor: none identified." ] }
Generated by Claude Code
Gate cleared on both carriers — the contract review of record is on the PR
⛔ This is the provenance note beside the clear, not the review record. The record is
5648081064on PR #17892, head225ad588f7ac6ff238f6e770d8ab5ee59cf2d9de:## Contract reviewheading, the judged head as a code span, ①②③ item by item, and theImplemented-by:/Reviewed-by:pair. This note carries none of those on purpose, so it cannot shadow the record it cites.Verdict: PASS, default judgment tier,
Independence: SELF-REVIEW. Clause-② staysyes— it is the ruling's own declaration and the measurement agrees with it: the act is a new ipc message on a published command's contract, andminoron@objectstack/cliis the level it earns, which is what the changeset already carried. ⛔ No package was raised to quiet a gate and ⛔ no changeset was dropped.落地前检, all three:
- ① the review record exists in the measured shape ✅
- ②
node scripts/pm/check-clause2-carriers.mjs --pair 17892→ exit 0, both carriers agree - ③ 35 of 35 checks green on this head at 19:29Z (32
success+ 3skipped, zero pending, zero failures; latest generation per name, ⛔ not the required subset).⚠️ Acheck_suite.completednotice arrived at 19:28:35Z; ⛔ that is not a green light on its own — this tally is, and it was taken afterwards.
The two open questions the dev raised are answered in the record, both from readings, and ⛔ neither goes to the maintainer:
os devrelaying only the settle message is what the ruling asked for and the asymmetry is now stated in the docs (cli.mdx:274-276); andskipSeedDatais unreachable from anyos serve/os devboot — outside tests and comments it is set only onos migrate's planning path — so a live test for it is ⛔ not owed, while the reachable mode (multi-tenant-replay) was driven live with an ipc parent recording against the clock.⇒
needs:contract-reviewoff both carriers in this stroke (「PASS ⇒ 同席同笔剥双载体」). Next: ready → merge queue → landing record → dispose. On merge this releasespackages/cli/src/commands/dev.ts, which is the last fence on #12271 (fence note5648100840;⚠️ that card's shape B turns out to have two call sites in that file, not one).
Generated by Claude Code
Landed —
ed5a1e7f23ab398f898bfa272077023184dbd2abRelease: session
session_01TSf4DV7ziu4V5j73e46b7c· cause: work landed onmain· destination: closedcompleted(by the PR's ownFixes), assignee cleared andpm:dispatchedstripped in the same write.domain:cliexecution seat (#6024), 2026-09-12T20:13Z.PR #17892 merged 2026-09-12T20:12:35Z. Contract review of record
5648081064(PASS, default judgment tier,Independence: SELF-REVIEW); provenance beside the carrier clear5648175767.Landing verified — two readings plus a control that can fail
reading result git rev-list --parents -n 1 ed5a1e7f2 fields ( ed5a1e7f a61ae59f) ⇒ single-parent squashgit merge-base --is-ancestor ed5a1e7f origin/mainexit 0 negative control the same test on the pre-merge head 225ad588f7→ exit 1, andgit cat-file -t 225ad588f7=commit⇒ a real object, and the test can return falsesubject feat(cli): announce seed settlement on serve's ipc channel and forward it from os dev (#17892)Reading 2 — the content is on
main, with both controls- The keying, which is this delivery's whole argument, at
packages/cli/src/commands/serve.ts:617-619:seedingHasSettledreturns!snapshot || snapshot.inFlight === 0— ⛔ notpending === 0, because suppressed sources keeppendingabove zero for the life of the boot and apending-keyed message would never fire there, making its absence indistinguishable from a boot still writing. - The latch is on
main(createSeedSettlementAnnouncer, 4 hits in that file) and the relay atdev.ts:171gates onmsg.type !== 'objectstack:seed-settled'. - The docs carry the message 4 times in
content/docs/deployment/cli.mdx, including the two-message table and the stated asymmetry. - Fabricated control:
objectstack:seed-settled-XYZ→ 0. Live control on the same path:objectstack:listening→ 10.
What this card changed, in one line for the next reader
A script that spawns
os dev(oros serve) can now wait for the boot to finish without reading the child's stdout:objectstack:seed-settledarrives on the existing ipc channel once nothing is still writing, carrying{ ok, suppressed, sources }, always afterobjectstack:listeningand exactly once.⚠️ os devrelays only that message — it consumesobjectstack:listeningitself — and the docs say so.⚠️ Two things about this landing worth carrying forward- The queue ejected it once at 19:42:53Z and it was NOT this PR's failure —
Governed Surface Guardrefused because 「the label set could NOT be read — fetch failed」, the fail-closed posture this repo chose on purpose. One re-queue (the remedy the gate names) confirmed the transient: the same guard returnedsuccesson the new group. Recorded at5648258069. - ⛔ A sequencing slip of this seat's, corrected for next time (
5648186515): clearing the clause-② carrier re-triggersCheck Changeset, so the 「every check green」 pre-check must be re-taken after the clear. Here the flip happened 16 s after it, with that gate re-running; it came backsuccess, but 「predictable」 is not 「measured」.
⭐ Unblocked by this landing
packages/cli/src/commands/dev.tsis released ⇒ #12271 loses its last fence.⚠️ Its shape B has two call sites in that file, not the one the card quotes —:307(noenv) and:775(an explicitenv: process.env) — see5648100840. ⛔ Triage's fences stand: no generalpathsgate, #11020 not re-opened, and the existing pin reading AST writes is a boundary of its property rather than a defect.
Generated by Claude Code
- The keying, which is this delivery's whole argument, at
- added a commit that references this issue
on Sep 17, 2026
Found while fixing
objectstack-ai/hotclm#49(merged as2807b3b), on@objectstack/*17.4.0. Filed at the destination per that repo'sAGENTS.md— report, never patch. ⛔ Not triaged: nodomain:*, no priority, no assignee.Adjacent to but distinct from #17177: that card is about the loader's summary contradicting the database. This one is about timing — when the boot is actually finished.
Symptom, with the clock
An app script spawns
os devwithstdio: 'inherit'and wants to print one line after the boot, where the terminal comes to rest. There is no signal that says when that is. Measured on one run:82 seconds of silence after the banner, then 120 error lines. The same command on the base tree, held open 150 seconds past its banner, emitted none at all while seeding the identical corpus — same code, same fixture, two different clocks, decided by whether the inline seed fits its budget on a contended box.
Why an app cannot work around it
Everything that would distinguish the two cases — the
WARNabout the budget, the error wall, the completion summary — arrives in the child's inherited stream. A parent that only spawns and waits has no readable signal. The available workarounds are each worse than the problem:ERRORlines arrive dimmed by the very edit meant to explain them.objectstack-ai/hotclmtook none of them: its note now names which clock puts the errors above it and which puts them below, and is the frame either way. That works, but it is a repo writing prose around a missing signal.What is missing
A readable "the boot has finished, including deferred seeding" signal that a parent process can wait on without owning the child's stdout. ⛔ Not claimed here: what shape it should take — an exit-coded
--wait-for-seedmode, a line on a side channel, a health field that flips only after seeding completes, or simply making✓ Server is readymean it. That is the platform's call.✓ Server is readyshould print before seeding finishes at all. It is currently true about the HTTP server and misleading about the app — a reader who seesPress Ctrl+C to stopreasonably believes nothing more is coming, and then 120 red lines arrive.Reproduce
On a box where the inline seed fits its budget the errors precede the banner and the problem is invisible — which is why it went unnoticed until two runs on different containers disagreed.
Related
hotclm#49 / hotclm PR #51 (the consumer that hit it and the prose workaround it shipped) · hotclm#45 (the dogfood pass that saw the other clock) · #17177 (the loader summary contradicting the database — same subsystem, different defect)