Skip to content

RunResumeSessionManager: abort with DOMException AbortError, not custom Error - #1036

Merged
kojiwakayama merged 1 commit into
mainfrom
koji/framework-deslop-pass-1
Apr 14, 2026
Merged

kojiwakayama merged 1 commit into
mainfrom
koji/framework-deslop-pass-1

Conversation

@kojiwakayama

Copy link
Copy Markdown
Contributor

Summary

`RunResumeSessionManager.cancelRun` aborted the run's signal with `new RunCancelledError()` — a plain `Error` subclass with `name: 'RunCancelledError'`. When provider SDKs (Anthropic / OpenAI / Google) saw that as `signal.reason`, their internal background promises rejected with it; any one of those that wasn't `.catch`-ed crashed the host process via `unhandledRejection`.

This is the same bug class that crashed staging veryfront-agent pods (fixed in veryfront-agent#371). The framework version ships into every Veryfront app, so the blast radius here is much bigger.

Fix

Abort with a `DOMException('Run cancelled', 'AbortError')` so every fetch/stream consumer recognises it as cancellation. Keep `RunCancelledError` on the `waitForSignal` reject path so callers can still `instanceof` it — that promise is awaited inline, so it's safe.

Tests (red→green TDD)

  • New: signal.reason is `DOMException` with `name === 'AbortError'`
  • Pinned: in-flight `waitForSignal` callers still reject with `RunCancelledError` after `cancelRun` (preserves the existing API)

Follow-up flagged

`src/react/compat/ssr-adapter/stream-renderer.ts:50` has the same shape: `controller.abort(new Error('SSR timeout: ...'))`. Lower impact (the local `onError` falls through harmlessly) but still wrong — the abort-vs-error distinction never matches in the local handler. Out of scope for this PR; happy to do a follow-up.

Test plan

  • `deno test --no-check --allow-all src/agent/runtime/resume-session.test.ts` — all 9 steps pass
  • Wider runtime suite (14 files / 178 steps) passes
  • Bumped to 0.1.203

@kojiwakayama
kojiwakayama force-pushed the koji/framework-deslop-pass-1 branch from eb4e8a5 to 1786e0c Compare April 14, 2026 21:42
…lass

RunResumeSessionManager.cancelRun aborted the run's signal with `new
RunCancelledError()`, the framework's plain `Error` subclass with
`name: 'RunCancelledError'`. When provider SDKs (e.g. Anthropic via
fetch) saw that as `signal.reason`, their internal background promises
rejected with it; if anyone failed to chain a `.catch()` on those, the
host process died via unhandledRejection.

This is the same bug class that crashed the staging veryfront-agent
pods (veryfront-agent#371). The framework version ships into every
Veryfront app, so the blast radius here is much bigger.

Abort with a DOMException(AbortError) instead — every fetch / stream
consumer recognises that as a normal cancellation. Keep
RunCancelledError on the waitForSignal reject path so callers can
still `instanceof` it; that promise is awaited inline so it's safe.

Test pins both: signal.reason is the AbortError shape, and an in-flight
waitForSignal still rejects with RunCancelledError.

Also bumps to 0.1.203.
@kojiwakayama
kojiwakayama force-pushed the koji/framework-deslop-pass-1 branch from 1786e0c to ba14b7f Compare April 14, 2026 21:45
@kojiwakayama
kojiwakayama enabled auto-merge (squash) April 14, 2026 21:46
@kojiwakayama
kojiwakayama merged commit 1c12ffe into main Apr 14, 2026
18 checks passed
@kojiwakayama
kojiwakayama deleted the koji/framework-deslop-pass-1 branch April 14, 2026 21:52
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.

1 participant