can1357/oh-my-pi · info · Error

Handoff aborted by session

Error message

Handoff aborted by session

What it means

throwIfHandoffAborted is the catch-all abort translation: when the signal's reason is neither a plain AbortError, nor an Error, nor a non-empty string, it throws Error("Handoff aborted by session"). This indicates the owning AgentSession aborted the handoff in a way that carried no descriptive reason.

Source

Thrown at packages/coding-agent/src/session/session-handoff.ts:34

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[];
	setSkipPostTurnMaintenance(timestamp: number | undefined): void;
	obfuscateTextForProvider(text: string | undefined): string | undefined;
	deobfuscateFromProvider(text: string): string;
	convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]>;

View on GitHub (pinned to 9690622007)

Solutions

  1. Treat as a session-level cancellation: stop handoff work and clean up
  2. Abort with a descriptive Error or string reason at the abort site so callers get a meaningful message
  3. Catch this error and retry the handoff with a new AbortController if the session is active again
  4. Audit the code paths that own the controller to see why the session aborted

Example fix

// before
controller.abort(true); // opaque -> 'Handoff aborted by session'
// after
controller.abort(new Error("Session shutting down")); // descriptive reason
Defensive patterns

Strategy: try-catch

Validate before calling

if (handoffSignal.aborted) {
  console.warn("Handoff aborted before start:", handoffSignal.reason);
  return;
}

Try / catch

try {
  await handoff.generateDocument(handoffSignal);
} catch (err) {
  if (err instanceof Error && err.message === "Handoff aborted by session") {
    // session-level teardown: stop work, no retry
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: generateDocument's throwIfHandoffAborted check runs after signal.abort() was called with a reason that is not an Error/DOMException/string (e.g. abort(true) with a non-string truthy value, or undefined).

Common situations: Session shutdown/teardown aborting in-flight handoff generation; custom abort reasons of unexpected type; library code aborting controllers without a reason.

Related errors


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