fix(sdk): report the missing done(), not the step the author awaited (#183) - #187
Conversation
#183. A body that awaited its only step and forgot `done()` was refused as `unawaited_step`, naming the step it had awaited: unawaited_step: flow "missing-completion" returned with unawaited steps: run-1 (f.run) The step was awaited. The defect was the missing completion, and there is a code for exactly that. Cause, found by probing rather than by reading: `isHandled` decides whether an operation was consumed by asking whether the COMPLETION depends on it, and returns false outright when there is no completion: isHandled(operation) { const completion = this.completionAsyncId; ... if (completion === undefined || ...) return false; With no `done()` there is no completion async id to trace from, so every operation is unhandled by construction. `verifyAuthoredOperations` ran before the completion check, computed an answer its own precondition did not support, and reported the symptom as the cause. The completion check now runs first, stopping operations and closing the lifecycle the way the body-failure path already does. A body that both forgets `done()` AND leaves a step unawaited now reports the missing completion. That is the honest order rather than a convenience: the unawaited-step verdict is not computable until there is a completion to compute it against, and once the author adds `done()` the verification runs normally and catches it. `requires an explicit completion after journal-backed steps` goes back to asserting `missing_completion`, the code it asserted before #140 dropped it. #184 had to weaken it to the refusal's class precisely because of this bug. Evidence. Commands runnable from the repository root; output below is verbatim, nothing removed: $ shasum -a 256 sdk/src/authored-flow-executor.ts b7eed1ca1197fd38dd01cfd4199be5883031fd7b26d1fc6f9a61dc8fb100a211 sdk/src/authored-flow-executor.ts $ (cd sdk && ./node_modules/.bin/vitest run tests/authored-flow.test.ts -t "requires an explicit completion") RUN v2.1.9 /Users/khaliqgant/AgentWorkforce/flows-183/sdk ✓ tests/authored-flow.test.ts (23 tests | 22 skipped) 5ms Test Files 1 passed (1) Tests 1 passed | 22 skipped (23) Start at 20:38:36 Duration 398ms (transform 92ms, setup 0ms, collect 167ms, tests 5ms, environment 0ms, prepare 38ms) # MUTATION: verification moved back BEFORE the completion check (the #183 order) $ shasum -a 256 sdk/src/authored-flow-executor.ts a999fbdcf998330718fd91fa9ff3711a3c6ea3d68cbbf585b5f041e649747178 sdk/src/authored-flow-executor.ts $ (cd sdk && npm --userconfig /private/tmp/claude-501/-Users-khaliqgant-Projects-AgentWorkforce-chief/c228933d-4f94-4d83-9a9a-daf3c83b94f1/scratchpad/empty-npmrc run build >/dev/null 2>&1; ./node_modules/.bin/vitest run tests/authored-flow.test.ts -t "requires an explicit completion") RUN v2.1.9 /Users/khaliqgant/AgentWorkforce/flows-183/sdk ❯ tests/authored-flow.test.ts (23 tests | 1 failed | 22 skipped) 9ms × authored flow journal executor > requires an explicit completion after journal-backed steps 7ms → expected AuthoredFlowExecutionError: unawaited_ste… { …(3) } to match object { code: 'missing_completion' } (3 matching properties omitted from actual) ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ FAIL tests/authored-flow.test.ts > authored flow journal executor > requires an explicit completion after journal-backed steps AssertionError: expected AuthoredFlowExecutionError: unawaited_ste… { …(3) } to match object { code: 'missing_completion' } (3 matching properties omitted from actual) - Expected + Received - Object { - "code": "missing_completion", + AuthoredFlowExecutionError { + "code": "unawaited_step", } ❯ tests/authored-flow.test.ts:477:7 475| // `done()` they forgot (#183). Fixed by checking the completion… 476| // so this can pin the code again. 477| await expect(executeAuthoredFlow(handle, client)).rejects.toMatc… | ^ 478| code: 'missing_completion', 479| }); ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ Test Files 1 failed (1) Tests 1 failed | 22 skipped (23) Start at 20:38:38 Duration 323ms (transform 80ms, setup 0ms, collect 125ms, tests 9ms, environment 0ms, prepare 34ms) # RESTORED (rebuilt from main and re-patched, not un-edited) $ shasum -a 256 sdk/src/authored-flow-executor.ts b7eed1ca1197fd38dd01cfd4199be5883031fd7b26d1fc6f9a61dc8fb100a211 sdk/src/authored-flow-executor.ts Full SDK suite at this head: 33 test files total -- 32 passed, 1 skipped -- and 665 tests total: 662 passed, 3 skipped. Typecheck clean. An earlier draft of this message called an abbreviated snippet "output complete" and wrote "32 files" for a total of 33. Both were rejected by review, fairly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR Session-Id: c228933d-4f94-4d83-9a9a-daf3c83b94f1
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughAuthored-flow finalization now checks for ChangesAuthored-flow completion handling
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. Comment |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR Session-Id: c228933d-4f94-4d83-9a9a-daf3c83b94f1
closed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR Session-Id: c228933d-4f94-4d83-9a9a-daf3c83b94f1
Nothing verified main. `cloud-runtime-artifact.yml` triggered only on `pull_request` and `workflow_dispatch`, and `review-swarm.yml` only on `pull_request` — so every PR is checked at its own head and never as merged, and since we squash-merge onto a main that has moved since that CI ran, the composed result went unverified. Not theoretical: twelve PRs merged on 2026-09-05 across the exactly-once claim path (#171, #182), resume adoption (#177, #186), the authored-flow executor (#184, #187) and the CLI run loop (#180) — each green on its own branch, the composed tree never run until dispatched by hand: ``` run 33987924703 workflow_dispatch main completed/success ed917bf ``` Main is fine. But nobody knew that, and finding out required knowing to ask. Without this, a bad compose surfaces as an unrelated PR going red — the most expensive way to find it, since the author debugs their own change first. Tonight already produced three failures on PRs that belonged to something else (#179, #185, the #174 chain), and each cost a tick to attribute. Paths are deliberately not filtered on the push trigger: on a PR the question is "does this change affect the runtime", on main it is "is the tree good", and a docs-only merge can land on a tree someone else broke. Evidence at the merged head d1cb32a: - signoff: local 3-lens preswarm, maintainability / history / structure all REVIEW_PASSED - CI: run 33988314276 success on d1cb32a, verified by headSha and event=pull_request - YAML parsed and asserted: triggers are `pull_request`, `push`, `workflow_dispatch`; push is branch-scoped to main; the pull_request path filter is unchanged
Fixes #183.
A body that awaited its only step and forgot
done()was refused as:The step was awaited. The defect was the missing completion, and there is a code for exactly that.
Cause
Found by probing, not by reading.
isHandleddecides whether an operation was consumed by asking whether the completion depends on it, and returns false outright when there is no completion:With no
done()there is no completion async id to trace from, so every operation is unhandled by construction.verifyAuthoredOperationsran before the completion check, computed an answer its own precondition did not support, and reported the symptom as the cause — telling the author to await something they had already awaited, and saying nothing about what they actually forgot.Fix
The completion check runs first, stopping operations and closing the lifecycle the way the body-failure path already does.
A body that both forgets
done()and leaves a step unawaited now reports the missing completion. That is the honest order rather than a convenience: the unawaited-step verdict is not computable until there is a completion to compute it against, and once the author addsdone()the verification runs normally and catches it.Test
requires an explicit completion after journal-backed stepsgoes back to assertingmissing_completion— the code it asserted before #140 dropped it. #184 had to weaken it to the refusal's class precisely because of this bug, so this closes that loop too.Mutation: moving verification back before the completion check fails exactly that test with
expected AuthoredFlowExecutionError: unawaited_ste… to match object { code: 'missing_completion' }. The full transcript, with runnable commands and verbatim output, is in the commit message; sha256b7eed1cabefore, restored tob7eed1caafter (rebuilt from main and re-patched rather than un-edited).Full SDK suite at this head: 33 test files total — 32 passed, 1 skipped — and 665 tests total: 662 passed, 3 skipped. Typecheck clean.
Signoff: local 3-lens preswarm, maintainability / history / structure all REVIEW_PASSED.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR