can1357/oh-my-pi · error · Error

No model selected for handoff

Error message

No model selected for handoff

What it means

SessionHandoff.generateDocument needs an active model to run the handoff generation turn; if session.model() returns undefined it throws immediately before any LLM call. A handoff cannot proceed without a selected model.

Source

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

		const sourceSignal = options?.signal;
		const onSourceAbort = () => {
			if (!handoffSignal.aborted) {
				handoffAbortController.abort(sourceSignal?.reason);
			}
		};
		if (sourceSignal) {
			sourceSignal.addEventListener("abort", onSourceAbort, { once: true });
			if (sourceSignal.aborted) {
				onSourceAbort();
			}
		}

		try {
			throwIfHandoffAborted(handoffSignal);

			const model = this.#host.model();
			if (!model) {
				throw new Error("No model selected for handoff");
			}
			const apiKey = await this.#host.modelRegistry.getApiKey(model, this.#host.sessionId());
			if (!apiKey) {
				throw new Error(`No API key for ${model.provider}`);
			}

			// Build the handoff request through the SAME pipeline a live turn uses
			// (`runEphemeralTurn` / `/btw` share it) so the oneshot reads the
			// provider prompt cache the main turn populated instead of cold-missing
			// the whole prefix: identical system prompt, normalized tools, and
			// transform-/obfuscation-matched message history via
			// `convertMessagesToLlm` + `buildSideRequestContext`, plus the live turn's
			// effective provider cache key with a unique side `sessionId` so
			// OpenAI/Codex append-only state never mixes with the live turn.
			const cacheSessionId = this.#host.sessionId();
			// The loop sends `promptCacheKey` (providerPromptCacheKey) and falls back to
			// the provider session id; providers route on `promptCacheKey ?? sessionId`.
			// Both can diverge from this.#host.sessionId() (tan/subagent/shared sessions), so

View on GitHub (pinned to 9690622007)

Solutions

  1. Set a model on the session (e.g. via setModel) before invoking handoff generation
  2. Guard: if (!session.model()) select a default model first
  3. Restore/initialize session model state properly after reset or deserialization
  4. Catch and surface 'select a model first' to the user instead of attempting handoff

Example fix

// before
await handoff.generateDocument(signal); // throws if no model
// after
if (!session.model()) {
  await controls.setModel({ model: defaultModel });
}
await handoff.generateDocument(signal);
Defensive patterns

Strategy: validation

Validate before calling

if (!session.model()) {
  await controls.setModel({ model: defaultModel }); // or surface a picker
}
await handoff.generateDocument(signal);

Try / catch

try {
  await handoff.generateDocument(signal);
} catch (err) {
  if (err instanceof Error && err.message === "No model selected for handoff") {
    // prompt user to select a model, then retry
  } else throw err;
}

Prevention

When it happens

Trigger: Calling generateDocument on a session whose model is not set (e.g. before initial model selection, or after a model was cleared/reset).

Common situations: Triggering compaction/handoff on a fresh or reset session before a model was ever chosen; model state lost after session restore; programmatic use of the handoff API without configuring a model first.

Related errors


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