can1357/oh-my-pi · info · Error
Handoff cancelled
Error message
Handoff cancelled
What it means
throwIfHandoffAborted translates an aborted AbortSignal into a typed error for the handoff flow. When the signal was aborted with a plain DOMException AbortError (no richer reason), it throws Error("Handoff cancelled") so callers can distinguish a user/environment cancellation from other abort causes.
Source
Thrown at packages/coding-agent/src/session/session-handoff.ts:30
import type { Message, Model, ServiceTier, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
import { logger, Snowflake } from "@oh-my-pi/pi-utils";
import type { ModelRegistry } from "../config/model-registry";
import type { Settings } from "../config/settings";
import { obfuscateProviderContext } from "../secrets/message-transform";
import type { SecretObfuscator } from "../secrets/obfuscator";
import type { HandoffResult, SessionHandoffOptions } from "./agent-session-types";
import type { SessionManager } from "./session-manager";
function createHandoffFileName(date = new Date()): string {
const fileTimestamp = date.toISOString().replace(/[:.]/g, "-");
return `handoff-${fileTimestamp}.md`;
}
function throwIfHandoffAborted(signal: AbortSignal): void {
if (!signal.aborted) return;
const reason = signal.reason;
if (reason instanceof DOMException && reason.name === "AbortError") {
throw new Error("Handoff cancelled");
}
if (reason instanceof Error) throw reason;
if (typeof reason === "string" && reason.length > 0) throw new Error(reason);
throw new Error("Handoff aborted by session");
}
/** Capabilities borrowed from the owning AgentSession. */
export interface SessionHandoffHost {
agent: Agent;
sessionManager: SessionManager;
settings: Settings;
modelRegistry: ModelRegistry;
sideStreamFn: StreamFn;
obfuscator: SecretObfuscator | undefined;
model(): Model | undefined;
thinkingLevel(): ThinkingLevel | undefined;
sessionId(): string;
baseSystemPrompt(): string[];View on GitHub (pinned to 9690622007)
Solutions
- Treat this as an expected cancellation: catch and return quietly instead of retrying
- If the handoff should proceed, restart it with a fresh, non-aborted AbortController
- Find what aborted the signal (user action, timeout, teardown) and remove or delay that abort
- Surface a 'cancelled' status to the user rather than an error report
Example fix
// before
await handoff.generateDocument(...); // throws 'Handoff cancelled'
// after
try {
await handoff.generateDocument(...);
} catch (err) {
if (err.message === "Handoff cancelled") return; // expected cancel
throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (handoffSignal.aborted) return; // already cancelled; skip the call
Try / catch
try {
await handoff.generateDocument(handoffSignal);
} catch (err) {
if (err instanceof Error && err.message === "Handoff cancelled") return; // expected
throw err;
} Prevention
- Check signal.aborted before starting handoff work
- Distinguish cancellation from failure in your error handling
- Abort with descriptive reasons when you own the controller
- Avoid aborting handoff signals during routine UI interactions
When it happens
Trigger: generateDocument calling throwIfHandoffAborted(handoffSignal) after the signal was aborted via controller.abort() with no custom reason (default AbortError).
Common situations: User cancelled the handoff prompt; session teardown or timeout aborted the controller mid-generation; upstream compaction logic aborted the handoff attempt.
Related errors
- Handoff aborted by session
- Request was aborted
- Auth broker request aborted
- OAuth refresh ownership aborted by caller
- Request was aborted.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/657cb20f26b4d2cb.
Report an issue: GitHub.