{"record":{"id":"52de72e0ec90fa11","repo":"can1357/oh-my-pi","slug":"no-model-selected-for-handoff","errorCode":null,"errorMessage":"No model selected for handoff","messagePattern":"No model selected for handoff","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/session-handoff.ts","lineNumber":119,"sourceCode":"\t\tconst sourceSignal = options?.signal;\n\t\tconst onSourceAbort = () => {\n\t\t\tif (!handoffSignal.aborted) {\n\t\t\t\thandoffAbortController.abort(sourceSignal?.reason);\n\t\t\t}\n\t\t};\n\t\tif (sourceSignal) {\n\t\t\tsourceSignal.addEventListener(\"abort\", onSourceAbort, { once: true });\n\t\t\tif (sourceSignal.aborted) {\n\t\t\t\tonSourceAbort();\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tthrowIfHandoffAborted(handoffSignal);\n\n\t\t\tconst model = this.#host.model();\n\t\t\tif (!model) {\n\t\t\t\tthrow new Error(\"No model selected for handoff\");\n\t\t\t}\n\t\t\tconst apiKey = await this.#host.modelRegistry.getApiKey(model, this.#host.sessionId());\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(`No API key for ${model.provider}`);\n\t\t\t}\n\n\t\t\t// Build the handoff request through the SAME pipeline a live turn uses\n\t\t\t// (`runEphemeralTurn` / `/btw` share it) so the oneshot reads the\n\t\t\t// provider prompt cache the main turn populated instead of cold-missing\n\t\t\t// the whole prefix: identical system prompt, normalized tools, and\n\t\t\t// transform-/obfuscation-matched message history via\n\t\t\t// `convertMessagesToLlm` + `buildSideRequestContext`, plus the live turn's\n\t\t\t// effective provider cache key with a unique side `sessionId` so\n\t\t\t// OpenAI/Codex append-only state never mixes with the live turn.\n\t\t\tconst cacheSessionId = this.#host.sessionId();\n\t\t\t// The loop sends `promptCacheKey` (providerPromptCacheKey) and falls back to\n\t\t\t// the provider session id; providers route on `promptCacheKey ?? sessionId`.\n\t\t\t// Both can diverge from this.#host.sessionId() (tan/subagent/shared sessions), so","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/session-handoff.ts#L101-L137","documentation":"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.","triggerScenarios":"Calling generateDocument on a session whose model is not set (e.g. before initial model selection, or after a model was cleared/reset).","commonSituations":"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.","solutions":["Set a model on the session (e.g. via setModel) before invoking handoff generation","Guard: if (!session.model()) select a default model first","Restore/initialize session model state properly after reset or deserialization","Catch and surface 'select a model first' to the user instead of attempting handoff"],"exampleFix":"// before\nawait handoff.generateDocument(signal); // throws if no model\n// after\nif (!session.model()) {\n  await controls.setModel({ model: defaultModel });\n}\nawait handoff.generateDocument(signal);","handlingStrategy":"validation","validationCode":"if (!session.model()) {\n  await controls.setModel({ model: defaultModel }); // or surface a picker\n}\nawait handoff.generateDocument(signal);","typeGuard":null,"tryCatchPattern":"try {\n  await handoff.generateDocument(signal);\n} catch (err) {\n  if (err instanceof Error && err.message === \"No model selected for handoff\") {\n    // prompt user to select a model, then retry\n  } else throw err;\n}","preventionTips":["Always select a model when initializing/restoring a session","Check session.model() before any model-dependent operation","Default to a configured model instead of leaving model unset","Handle session resets by re-establishing model state"],"tags":["model-selection","handoff","compaction","state"],"backgroundTag":"no-model-selected","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}