Skip to content

feat: distinguish cancelled steps with step:cancel event#48

Merged
coji merged 5 commits into
mainfrom
feat/step-cancel-event
Mar 5, 2026
Merged

feat: distinguish cancelled steps with step:cancel event#48
coji merged 5 commits into
mainfrom
feat/step-cancel-event

Conversation

@coji
Copy link
Copy Markdown
Owner

@coji coji commented Mar 5, 2026

Summary

  • Cancelled steps are now recorded as cancelled instead of failed in the database
  • New step:cancel event fires when a step is interrupted by run cancellation
  • Non-cancel errors (user code exceptions, user-initiated fetch aborts) remain step:fail
  • Detection uses controller.signal.aborted to distinguish cancellation from other errors

Closes #39

Changes

  • schema.ts, storage.ts: Add 'cancelled' to step status type
  • events.ts, index.ts: Add StepCancelEvent interface and export
  • context.ts: Check controller.signal.aborted in catch block to emit correct event
  • server.ts: Stream step:cancel via SSE to React clients
  • durably-react: Handle step:cancel in both browser and client mode hooks
  • Docs: llms.md, events.md, llms.txt updated

Test plan

  • New test: cancelled step emits step:cancel (not step:fail)
  • New test: non-cancel errors still emit step:fail
  • pnpm validate passes (format, lint, typecheck, all tests)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for step cancellation events (step:cancel) with real-time subscriptions and automatic UI refresh on cancel.
    • Steps can now have a 'cancelled' status alongside 'completed' and 'failed'.
  • Documentation

    • Added docs and examples for the step:cancel event and updated StepRecord/Step status docs.
  • Tests

    • Added tests validating step:cancel emission and distinction from failures.

…event (#39)

When a run is cancelled while a step is executing, the step is now
recorded as 'cancelled' instead of 'failed' and emits a 'step:cancel'
event instead of 'step:fail'. Detection uses controller.signal.aborted
so user-initiated AbortErrors (e.g. fetch timeouts) are unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
durably-demo Ready Ready Preview Mar 5, 2026 3:24am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 5, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds explicit handling for aborted steps: records step status as cancelled, emits a new step:cancel event, extends event types/schema/storage to include cancelled, and wires the event through server SSE, client hooks, tests, docs, and examples.

Changes

Cohort / File(s) Summary
Event & Public Types
packages/durably/src/events.ts, packages/durably/src/index.ts, packages/durably-react/src/types.ts
Introduce StepCancelEvent and include step:cancel in the DurablyEvent union and related input/type exports.
Schema & Storage Types
packages/durably/src/schema.ts, packages/durably/src/storage.ts, packages/durably-react/src/client/use-run-actions.ts
Extend step status unions from `'completed'
Step Execution Logic
packages/durably/src/context.ts
Detect controller.signal.aborted in step catch path; persist status: 'cancelled', emit step:cancel, and throw CancelledError instead of treating as failure.
Server SSE & Subscriptions
packages/durably/src/server.ts
Runs subscription stream now enqueues and forwards step:cancel events (filtered by jobName/labels like other step events).
Client Hooks & Runtime
packages/durably-react/src/client/use-runs.ts, packages/durably-react/src/hooks/use-runs.ts
Subscribe to step:cancel in real-time listeners to trigger refreshes alongside existing step events.
Tests, Docs & Examples
packages/durably/tests/shared/step.shared.ts, packages/durably/docs/llms.md, website/api/events.md, website/public/llms.txt, website/api/durably-react/types.md, examples/.../*, examples/server-node/basic.ts
Add tests asserting step:cancel emission, update docs/examples to show step:cancel, and document the new 'cancelled' step status.
Developer Guidance / Checklists
.claude/skills/doc-check/SKILL.md
Update documentation checklist and change-type table to include job/step API, event type, and React hook changes.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Controller as AbortController
    participant Step as Step Executor
    participant Storage as Storage Layer
    participant Events as Event Bus
    participant Subscribers as Subscribers (SSE / Hooks)

    App->>Controller: abort()
    Controller->>Step: signal abort on running step
    Step->>Step: catch error
    Step->>Controller: check signal.aborted
    alt aborted == true
        Step->>Storage: persist step { status: "cancelled" }
        Storage-->>Step: saved
        Step->>Events: emit "step:cancel" (runId, jobName, stepName, stepIndex, labels)
    else
        Step->>Storage: persist step { status: "failed" }
        Storage-->>Step: saved
        Step->>Events: emit "step:fail"
    end
    Events->>Subscribers: deliver event
    Subscribers-->>Subscribers: refresh UI / consumers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through code where signals fade,
A gentle abort makes a softer shade.
Steps now bow with "cancelled" grace,
Events hop out and find their place.
Tiny telemetry, hopped into trace.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: distinguish cancelled steps with step:cancel event' directly and accurately summarizes the main change: introducing a new step:cancel event to distinguish cancelled steps from failed ones.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #39: detects cancellation via controller.signal.aborted, records cancelled status, emits step:cancel event, adds cancelled to status schema, defines StepCancelEvent type, and maintains backward compatibility with step:fail.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing step cancellation detection. Updates to example apps, documentation, and tests are all in scope as supporting changes for the new feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/step-cancel-event

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/durably-react/src/client/use-runs.ts (1)

45-51: Align step:cancel client event type with server payload.

Server SSE includes labels for step:cancel, but this variant drops it. Keeping the same shape avoids silent type drift and enables label-aware client logic later.

Suggested fix
   | {
       type: 'step:cancel'
       runId: string
       jobName: string
       stepName: string
       stepIndex: number
+      labels: Record<string, string>
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/durably-react/src/client/use-runs.ts` around lines 45 - 51, The
'step:cancel' client event variant in use-runs.ts is missing the labels field
present in the server SSE; update the union variant for type: 'step:cancel' (the
object with runId, jobName, stepName, stepIndex) to include a labels property
matching the server shape (e.g., labels: string[] or the same type used by the
server) so the client event type stays aligned with server payloads and enables
label-aware logic.
packages/durably/tests/shared/step.shared.ts (1)

508-539: Add one explicit AbortError-without-run-cancel test case.

This test covers generic non-cancel errors, but a direct AbortError case would better lock in the requirement that user-space abort errors still emit step:fail unless the run itself was cancelled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/durably/tests/shared/step.shared.ts` around lines 508 - 539, Add a
new test case similar to the existing "emits step:fail event for
non-cancellation errors" that specifically throws an AbortError (using the
AbortError type/constructor) from inside the step callback (via defineJob -> run
-> step.run) but not as a result of cancelling the run; register the job and
attach listeners to 'step:fail' and 'step:cancel', trigger and start the job,
and assert that a single step:fail event is emitted with the stepName matching
and error message matching the AbortError while no step:cancel events are
emitted. Ensure you reference the same symbols used in the file: defineJob, the
job's run function calling step.run, the 'step:fail' and 'step:cancel' event
handlers, and the AbortError type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/durably-react/src/types.ts`:
- Around line 96-102: The 'step:cancel' union variant in
packages/durably-react/src/types.ts is missing the labels property used by
core/server; update the 'step:cancel' payload shape (the object with type:
'step:cancel', runId, jobName, stepName, stepIndex) to include labels:
Record<string, string> (matching other event variants) so React consumers get
typed access to labels, and adjust any related type exports/usages to accept the
new property.

In `@packages/durably/src/context.ts`:
- Around line 122-146: When isCancelled is true in the catch block that emits
the 'step:cancel' event, rethrow a cancellation-specific error instead of the
original error: replace the existing "throw error" after the
eventEmitter.emit(...) in the cancellation branch with throwing a CancelledError
(or instantiating the project's CancelledError class) so upstream cancellation
handling sees the correct type; ensure other branches still rethrow the original
error. Reference: isCancelled, eventEmitter.emit (type: 'step:cancel'), and the
current "throw error" statement.

In `@website/api/events.md`:
- Around line 220-237: The docs add a `step:cancel` event but the Type
Definitions union example still omits StepCancelEvent; update the DurablyEvent
union example to include the new StepCancelEvent type (i.e., add StepCancelEvent
to the DurablyEvent union alongside existing types) and ensure the
StepCancelEvent shape (runId, jobName, stepName, stepIndex, labels, timestamp,
sequence) matches the example shown in the `step:cancel` section so the Type
Definitions and event examples remain consistent.

---

Nitpick comments:
In `@packages/durably-react/src/client/use-runs.ts`:
- Around line 45-51: The 'step:cancel' client event variant in use-runs.ts is
missing the labels field present in the server SSE; update the union variant for
type: 'step:cancel' (the object with runId, jobName, stepName, stepIndex) to
include a labels property matching the server shape (e.g., labels: string[] or
the same type used by the server) so the client event type stays aligned with
server payloads and enables label-aware logic.

In `@packages/durably/tests/shared/step.shared.ts`:
- Around line 508-539: Add a new test case similar to the existing "emits
step:fail event for non-cancellation errors" that specifically throws an
AbortError (using the AbortError type/constructor) from inside the step callback
(via defineJob -> run -> step.run) but not as a result of cancelling the run;
register the job and attach listeners to 'step:fail' and 'step:cancel', trigger
and start the job, and assert that a single step:fail event is emitted with the
stepName matching and error message matching the AbortError while no step:cancel
events are emitted. Ensure you reference the same symbols used in the file:
defineJob, the job's run function calling step.run, the 'step:fail' and
'step:cancel' event handlers, and the AbortError type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49400391-4331-4858-a1d1-9562a1fb75a2

📥 Commits

Reviewing files that changed from the base of the PR and between a6258f4 and 836170b.

📒 Files selected for processing (13)
  • packages/durably-react/src/client/use-runs.ts
  • packages/durably-react/src/hooks/use-runs.ts
  • packages/durably-react/src/types.ts
  • packages/durably/docs/llms.md
  • packages/durably/src/context.ts
  • packages/durably/src/events.ts
  • packages/durably/src/index.ts
  • packages/durably/src/schema.ts
  • packages/durably/src/server.ts
  • packages/durably/src/storage.ts
  • packages/durably/tests/shared/step.shared.ts
  • website/api/events.md
  • website/public/llms.txt

Comment thread packages/durably-react/src/types.ts
Comment thread packages/durably/src/context.ts
Comment thread website/api/events.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@website/api/durably-react/types.md`:
- Around line 102-117: The exported StepRecord.status type in use-run-actions.ts
currently only allows 'completed' | 'failed'; update the StepRecord (or exported
type alias) to include 'cancelled' so its union becomes 'completed' | 'failed' |
'cancelled', matching the backend/storage schema and the docs; locate the
StepRecord/type definition in use-run-actions.ts (exported at the top around
line 9) and add the 'cancelled' member to the union.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b069c60d-d2b9-4f41-a3ed-3bbc33614091

📥 Commits

Reviewing files that changed from the base of the PR and between 836170b and 8bed946.

📒 Files selected for processing (1)
  • website/api/durably-react/types.md

Comment thread website/api/durably-react/types.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add labels to step:cancel in DurablyEvent type (types.ts)
- Add cancelled to StepRecord.status (use-run-actions.ts)
- Rethrow CancelledError instead of original error on cancellation (context.ts)
- Add StepCancelEvent to DurablyEvent union in docs (events.md)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add step:cancel event listener to server-node example and guides
- Handle cancelled step status with gray styling in dashboard examples
- Add example apps section to doc-check skill

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coji coji merged commit 07c5b9a into main Mar 5, 2026
4 checks passed
@coji coji deleted the feat/step-cancel-event branch March 5, 2026 12:14
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.

feat: distinguish cancelled steps from failed steps in telemetry

1 participant