{"record":{"id":"361091a20f9c16e6","repo":"can1357/oh-my-pi","slug":"snapcompact-cannot-run-locally-this-model-id","errorCode":null,"errorMessage":"snapcompact cannot run locally: ${this.#model.id} is text-only.","messagePattern":"snapcompact cannot run locally: (.+?) is text-only\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/session-maintenance.ts","lineNumber":865,"sourceCode":"\n\t\t\t// Focus instructions require an LLM summary, so the preference resolver\n\t\t\t// only selects snapcompact for an undirected manual compaction.\n\t\t\tconst wantsSnapcompact = compactionPrep.kind !== \"fromHook\" && selectedMethod === \"snapcompact\";\n\t\t\tconst snapcompactReady = wantsSnapcompact;\n\t\t\tconst snapcompactShapeSetting = this.#host.settings.get(\"snapcompact.shape\");\n\t\t\tlet snapcompactShape: snapcompact.Shape | undefined;\n\t\t\t// Claude refuses inputs that reproduce its own reasoning as text\n\t\t\t// (\"reasoning_extraction\"), and the snapcompact archive is replayed as\n\t\t\t// text into every later request; drop `¶think:` sections for\n\t\t\t// Anthropic-dialect targets (issue #6093).\n\t\t\tconst snapcompactIncludeThinking = preferredDialect(this.#model.id) !== \"anthropic\";\n\t\t\tif (wantsSnapcompact && !this.#model.input.includes(\"image\")) {\n\t\t\t\tthis.#host.emitNotice(\n\t\t\t\t\t\"warning\",\n\t\t\t\t\t`snapcompact needs a vision-capable model (${this.#model.id} is text-only)`,\n\t\t\t\t\t\"compaction\",\n\t\t\t\t);\n\t\t\t\tthrow new Error(`snapcompact cannot run locally: ${this.#model.id} is text-only.`);\n\t\t\t} else if (snapcompactReady) {\n\t\t\t\tconst text = snapcompact.serializeConversation(\n\t\t\t\t\tconvertToLlm(preparation.messagesToSummarize.concat(preparation.turnPrefixMessages)),\n\t\t\t\t\t{ includeThinking: snapcompactIncludeThinking },\n\t\t\t\t);\n\t\t\t\tconst probeText = snapcompact.renderabilityProbeText(\n\t\t\t\t\ttext,\n\t\t\t\t\tpreparation.previousPreserveData,\n\t\t\t\t\tpreparation.previousSummary,\n\t\t\t\t);\n\t\t\t\tsnapcompactShape = snapcompact.resolveShapeForText(probeText, this.#model, snapcompactShapeSetting);\n\t\t\t\tconst renderScan = snapcompact.scanRenderability(probeText, { shape: snapcompactShape });\n\t\t\t\tif (!renderScan.isSafe) {\n\t\t\t\t\tconst percent = (renderScan.unrenderableRatio * 100).toFixed(1);\n\t\t\t\t\tthis.#host.emitNotice(\n\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t`snapcompact disabled: unsupported characters for selected snapcompact font (${percent}%).`,\n\t\t\t\t\t\t\"compaction\",","sourceCodeStart":847,"sourceCodeEnd":883,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/session-maintenance.ts#L847-L883","documentation":"Thrown when snapcompact is selected but the active model's declared inputs do not include `image`. snapcompact archives history into dense bitmap images the model must read back, so it fundamentally requires a vision-capable model; with a text-only model it cannot run and the library fails fast after emitting a warning notice.","triggerScenarios":"Running `/compact snapcompact` (or the snapcompact method via methodOrder) while the selected model has `input` metadata lacking \"image\" — e.g. a text-only model like an older or reasoning-only variant.","commonSituations":"Switching to a text-only model then using a saved snapcompact default; discovery metadata misreporting modality so the user believes the model supports images; hardcoded snapcompact in scripts after switching providers.","solutions":["Select a vision-capable model (input includes \"image\") before running snapcompact.","Use `/compact soft` or `/compact remote` instead, which summarize as text.","If the model actually supports images, fix the model metadata (catalog/discovery) so `input` lists \"image\"."],"exampleFix":"// before\nawait session.compact({ mode: \"snapcompact\" }); // text-only model\n// after\nawait session.setModel(\"google/gemini-2.5-flash\"); // vision-capable\nawait session.compact({ mode: \"snapcompact\" });","handlingStrategy":"validation","validationCode":"const model = session.getModel();\nif (mode === \"snapcompact\" && model && !model.input.includes(\"image\")) {\n  throw new Error(`snapcompact needs a vision-capable model (${model.id} is text-only)`);\n}","typeGuard":"function isVisionCapable(model: { input: string[] }): boolean {\n  return model.input.includes(\"image\");\n}","tryCatchPattern":"try {\n  await session.compact({ mode: \"snapcompact\" });\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"snapcompact cannot run locally\")) {\n    return session.compact({ mode: \"soft\" }); // text-summary fallback\n  }\n  throw err;\n}","preventionTips":["Check `model.input` for \"image\" before choosing snapcompact.","Pick a vision-capable default model if snapcompact is your standard flow.","Distrust modality metadata for new models — verify vision support with a probe."],"tags":["compaction","snapcompact","model-capability","vision"],"backgroundTag":"model-capability-unsupported","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}