Skip to content

fix(sdk): report the missing done(), not the step the author awaited (#183) - #187

Merged
kjgbot merged 1 commit into
mainfrom
fix/183-missing-completion-before-verify
Sep 5, 2026
Merged

fix(sdk): report the missing done(), not the step the author awaited (#183)#187
kjgbot merged 1 commit into
mainfrom
fix/183-missing-completion-before-verify

Conversation

@kjgbot

@kjgbot kjgbot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #183.

A body that awaited its only step and forgot done() was refused as:

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, not 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 — 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 adds done() the verification runs normally and catches it.

Test

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, 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; sha256 b7eed1ca before, restored to b7eed1ca after (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

#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
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 1edc2ef5-5fc0-459a-83f1-a083e5793a28

📥 Commits

Reviewing files that changed from the base of the PR and between 092a25f and 5858102.

📒 Files selected for processing (2)
  • sdk/src/authored-flow-executor.ts
  • sdk/tests/authored-flow.test.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Authored-flow finalization now checks for done() before verifying unawaited operations. Missing completion stops tracked operations, closes the lifecycle, and throws missing_completion. The regression test asserts this error code.

Changes

Authored-flow completion handling

Layer / File(s) Summary
Completion validation and regression coverage
sdk/src/authored-flow-executor.ts, sdk/tests/authored-flow.test.ts
The executor validates completion before operation verification and performs cleanup before throwing missing_completion. The test checks the specific error code and documents the validation order.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A rabbit checks the flow at dawn
done() is found, or cleanup runs on
Unawaited steps wait their turn
A precise error helps tests learn
The lifecycle closes, and pages turn


Note

🎁 Summarized by CodeRabbit Free

Your 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 @coderabbitai help to get the list of available commands.

kjgbot pushed a commit that referenced this pull request Sep 5, 2026
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
@kjgbot
kjgbot merged commit f00d7ce into main Sep 5, 2026
3 of 4 checks passed
@kjgbot
kjgbot deleted the fix/183-missing-completion-before-verify branch September 5, 2026 19:04
kjgbot pushed a commit that referenced this pull request Sep 5, 2026
 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
kjgbot added a commit that referenced this pull request Sep 5, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A body that awaits its step but forgets done() is refused as unawaited_step

1 participant