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

  1. Treat this as an expected cancellation: catch and return quietly instead of retrying
  2. If the handoff should proceed, restart it with a fresh, non-aborted AbortController
  3. Find what aborted the signal (user action, timeout, teardown) and remove or delay that abort
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/657cb20f26b4d2cb. Report an issue: GitHub.