#3330·Archon

test(cli): the owner loop's signaled-event resume path has no test

Author: WirasmCreated Sep 14, 2026Updated Sep 14, 2026
Labelsarea: cli

Problem

The CLI owner loop added by #3316 has one untested branch: a signal arriving while the owning process sleeps through an event wait.

awaitDurableWaitDeadline polls the run row every 5 s and returns 'resume' as soon as the cursor reports signaled (packages/cli/src/commands/workflow.ts:3505, cursor built at :3476-3484). The integration suite covers the neighbouring paths — a duration wait resuming at its deadline, two sequential waits under one owner, an event wait expiring at its deadline (status: 'expired'), an attention wait releasing its owner, and a wait another process released — but nothing drives a real signal into a run while its owner is asleep and asserts the wait completes satisfied.

Both reviewers on #3316 named it independently: the pack review recorded it under evidence it could not obtain, and CodeRabbit raised it as a Minor functional-correctness finding.

Why it matters

It is the success path of event waits under CLI ownership. Everything asserted today is a wait that ends because time ran out; nothing asserts a wait that ends because the thing it was waiting for actually happened. A regression that made the poll miss signaledAt — or that let the superseded-cursor guard at :3513 treat a signal as a released wait and return 'stop' — would leave the run parked until its deadline and still pass the whole suite, looking like a slow success rather than a broken one.

The guard is the specific risk. It compares stepName and resumeAt and stops the loop when either moves. A signal is expected to change neither, so the branch depends on an implementation detail of how signalling writes the wait context, with no test pinning it.

Why now

#3316 merged as c15da5166, so the code is on dev untested. The gap was accepted as non-blocking for that PR because /signal requires a running server, which puts the path outside the CLI-only scenario #3312 was about. That reasoning bounds the risk; it does not cover the combination that remains reachable — a server running while a foreground or detached CLI process owns the run — and it will stop applying if signalling ever gains a non-server route.

Desired outcome

An integration test starts a run that pauses on an event wait with a distant deadline, signals it through the ordinary path while the owning CLI process is mid-sleep, and asserts the run resumes and completes with the wait node satisfied — not expired, and not still paused.

Invariants

  • The assertion is on observable run state (wait status and run terminal), not on the poll's internals, so a behaviour-preserving rewrite of the loop keeps passing.
  • The deadline in the fixture is far enough out that expiry cannot be what completes the run; a test that passes because the deadline fired proves nothing.
  • The existing event-wait expiry case stays, and stays distinguishable from this one.
  • No production behaviour change is in scope here. If the test proves the branch broken, that is a separate fix.

Acceptance

  • Seen red first: stubbing the signaled short-circuit at workflow.ts:3505 leaves the run paused until its deadline, and the new test fails.
  • After: the test passes against current dev, and the run reaches terminal with the event wait satisfied well before its deadline.
  • The superseded-cursor guard at :3513 is exercised by the same run rather than assumed — a signal must not read as a released wait.

Evidence

PR #3316 (merged as c15da5166), its review report, and CodeRabbit's outside-diff finding on packages/cli/src/commands/workflow.ts:3445-3575. Existing cases: packages/cli/src/commands/workflow-wait.integration.spec.ts:837, :863, :889, :935, :968.