Repository navigation
Description
waiting_until can only ever be NULL, and the MCP description promises it
One writer: agent-loop-store.ts:272-273, binding wait?.until ?? null. Three production call sites, none passes until:
coding-session.ts:440—null(a clear)coding-session.ts:581—{ reason: wait }coding-session.ts:713—{ reason: "platform_interrupt" }
Only run-liveness.test.ts:166 supplies one. This is the #570 pattern exactly: the helper is tested, the call sites are not, so the column reads as implemented while nothing populates it. Measured live: waitingUntil null on 89 of 89 runs, including one that parked ~6h51m by its own wall clock.
The instant exists and is thrown away
coding-wait.ts:210 returns until: now + ms, consumed at coding-pause.ts:174, and rendered into chat prose at coding-wait.ts:274,304. It never reaches recordLiveness, because pauseDeps(round, parkReason) closes over the reason only.
Two descriptions disagree, and the wrong one is the general-purpose read
composition.ts:255— "waiting(deliberately parked —waitNotesays what for and until when)"coding.ts:170— "saying what for"
The sibling that omits the promise is the correct one, which is the tell. work-report.ts:127's "expected to resume in …" clause is unreachable code.
Why this misleads
"Parked, resuming at 16:00" and "parked, indefinitely" are different decisions for the owner. The platform computes the first and reports the second.
Acceptance criteria
recordLivenessreceives the instantcoding-wait.ts:210already computes;waitingUntilis populated wherever a park has a known end.- A park with genuinely no known end stores NULL and the prose says so — the distinction is the point, not the field being non-null.
composition.ts:255matches behaviour, or behaviour matches it.- A guard asserts every writer of a nullable status column is exercised from a production call site, denominator stated per ADR 0002. A helper test that supplies an argument no caller supplies is the failure mode here and in
instance_runtime_nodes.statusis a write-once "online" column — every machine an instance has ever seen reports online forever, including one last seen four days ago #570. - Red demonstrated: an engine-limit park records its reset instant.
Verified vs inferred
- Verified: the single writer, all three call sites, the test-only supplier, both descriptions,
coding-wait.ts:210/coding-pause.ts:174, and the 89/89 live measurement. - Inferred: nothing.
Activity
fix(runs): a park states when it ends, because the instant was comput…
serge-ivo commentedon Aug 15, 2026
Landed —
4d4f52f, live in productionConfirmed in the code before changing anything: one writer (
agent-loop-store.ts:recordLiveness,binding
wait?.until ?? null), three call sites, none passinguntil, and exactly one callerthat did —
run-liveness.test.ts. Your measurement reproduced:waiting_untilnull on 89 of 89runs.
It was a plumbing fix, as the ticket said
planEngineWaitalready returnsuntil: now + ms(coding-wait.ts:210), it is already consumed incoding-pause.tsand already rendered into the owner's chat sentence. It never reachedrecordLivenessbecausePauseDeps.tickclosed over the reason alone. Soticktakes the park,waitForEngineResetpassesplan.until, and the workflow's tick binds it.Passed on every tick rather than once: the ticks are what survive a replay, so a park that
re-heartbeats after an eviction has to restate its end rather than blank it.
One call site deliberately does NOT get an
until, and this is the decision to checkThe human handoff has a computable 15-minute deadline — but it is when the run gives up, not
when it resumes, and
waiting_untilis read as a resume time (work-report.tsrenders it as"expected to resume in …"). Publishing a give-up time under a resume-time field would tell an owner
to wait for something that is about to stop.
waiting_reason: "human"is the honest half.If you want the human park to carry a deadline too, it needs a second field or a reason-aware
renderer — not this one. Happy to file that; I did not assume it.
platform_interruptpasses none for a different reason: Cloudflare replays the journal when itreplays it, and there is no instant to state.
AC4 — the guard is two-sided, because neither side alone can see this defect class
workers/api/src/lib/run-park-writers.test.ts, 7 arms:resolvePausedriven for real, and everytick of an engine-limit park asserted to carry
until; the human handoff asserted to carry none;recordLivenesssite enumerated from disk,the count asserted (3: one clear, two parks), and a park without an
untilmust be NAMED withthe reason it has no knowable end;
call site, denominator 2 (
reason,until);Verified red before green: reverting the two call-site lines fails 3 of the 7 arms, including
the general one. A test that supplies the argument by hand fails none of them — which is why one
existed and the column was still empty.
The description mismatch — not fixed here, as instructed
composition.ts:255promises "waitNotesays what for and until when";coding.ts:170saysonly "saying what for". With this change the wider promise becomes true for an
engine_limitpark and stays false for a
humanone, so the two descriptions are still not interchangeable andcomposition.tsis not mine to edit. Routing to you.Also worth naming:
work-report.ts's"expected to resume in …"clause was unreachable code and isnow reachable. The concurrent #588/#589 work landed a
RUN_HEALTH_LEGENDcarrying the same"and until when" promise — same status: true for
engine_limit, not forhuman.What is not yet verified
Nothing has parked since the deploy, so
waiting_untilis still null on all 89 rows. The writepath has unit coverage and a real-schema round-trip; the first engine-limit park after this deploy
is the production test. If it does not populate I will fix it forward.
coding-session.tsstayed line-neutral against its 872-line pin — the explanation lives incoding-pause.ts, which is where the instant is known — so no pin was raised and no seam was forced.CI green on
4d4f52f; API deploy31879980663succeeded.fix(runs): the health legend stops promising a resume time a human ha…
fix(runs): a park states WHICH kind of deadline it has, so the handof…
fix(coding): the instruction stamp remembers an owner turn from an ea…
fix(d1): one text format for every timestamp a `datetime('now')` pred…
serge-ivo commentedon Aug 18, 2026
Audit verdict — DELIVERED. The title's central claim is now refuted by production data. Closing.
The measurement this issue rests on —
waitingUntilnull on 89 of 89 runs — no longer holds.Live, read from
GET /v1/instances/bd43f4de-…/loopon 2026-08-1823 runs, of which 2 carry a non-null
waitingUntil:10b62cd4needs_humanhuman2670cecccancelledhumanThe column has a production writer and production values. That is the defect this issue named, and it is gone.
Per criterion
AC1 — the instant reaches
recordLiveness.workers/api/src/workflows/coding-session.ts:610(onHEAD):Passed on every tick, not once, so a park that re-heartbeats after an eviction restates its end rather than blanking it.
AC2 — a park with no knowable end stores NULL.
coding-session.ts:743still passes{ reason: "platform_interrupt" }with nountil, because Cloudflare replays the journal when it replays it and there is no instant to state.AC3 — the two descriptions agree. Settled by #596 rather than by picking a winner, and the resolution is better than what this issue asked for.
workers/mcp/src/instance-tools/composition.ts:263-271now records that a park's deadline is not one kind of thing:waitingUntilis when the clock RUNS OUT, andwaitingReasonsays what running out means — anengine_limitpark resumes at it, ahumanhandoff gives up at it. The tool text is rendered fromRUN_HEALTH_GLOSS, so the "and until when" promise cannot be retyped back in.AC4 — the guard exists and is two-sided.
workers/api/src/lib/run-park-writers.test.ts, 7 arms: the module that knows the instant hands it over; every productionrecordLivenesscall site enumerated from disk with the count asserted; and the general form — every optional park field supplied by at least one production call site. Red was watched: reverting the two call-site lines fails 3 of 7, while a test that supplies the argument by hand fails none — which is exactly why one existed and the column was still empty.AC5 — red demonstrated. At the unit level, above. In production, the two rows in the table.
One correction to this issue's own delivery comment, so it does not mislead
The
4d4f52fcomment said "One call site deliberately does NOT get anuntil… the human handoff", on the grounds that its 15-minute deadline is when the run gives up rather than when it resumes. #596 superseded that: a human handoff now DOES carry its give-up instant, andrun-park-writers.test.ts:73asserts it — "a human handoff ticks WITH its give-up instant, which is the deadline the owner can beat". That is why both live rows above arehumanparks. The reader-side ambiguity the original decision was protecting against is handled by the reason-aware gloss instead.What is genuinely not observed
No
engine_limitpark has occurred since the deploy, so that specific reason has not been seen carrying anuntilin production. The write path is one line shared by every reason and is unit-pinned; I am not treating its absence as an open defect, but recording it rather than implying coverage I did not measure.Verified vs inferred
HEAD(not the dirty working tree); the guard file and its human-park arm; the live run rows.