{"record":{"id":"3e2c05b1cef04f81","repo":"can1357/oh-my-pi","slug":"auto-thinking-unparseable-local-classification","errorCode":null,"errorMessage":"auto-thinking: unparseable local classification: ${JSON.stringify(text)}","messagePattern":"auto-thinking: unparseable local classification: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/auto-thinking/classifier.ts","lineNumber":176,"sourceCode":"\nasync function classifyLocal(input: string, modelKey: string, deps: ClassifyDifficultyDeps): Promise<Effort> {\n\tif (!isTinyMemoryLocalModelKey(modelKey)) {\n\t\tthrow new Error(`auto-thinking: unsupported local classifier model: ${modelKey}`);\n\t}\n\tconst maxTokens = isTinyMemoryReasoningModelKey(modelKey)\n\t\t? Math.max(LOCAL_ANSWER_MAX_TOKENS, LOCAL_REASONING_MAX_TOKENS)\n\t\t: LOCAL_ANSWER_MAX_TOKENS;\n\tconst builtPrompt = prompt.render(difficultyLocalPrompt, { prompt: input });\n\tconst text = await tinyModelClient.complete(modelKey, builtPrompt, {\n\t\tmaxTokens,\n\t\tsignal: deps.signal,\n\t});\n\tif (!text) {\n\t\tthrow new Error(\"auto-thinking: local classification returned no output\");\n\t}\n\tconst effort = parseDifficultyBucket(text);\n\tif (!effort) {\n\t\tthrow new Error(`auto-thinking: unparseable local classification: ${JSON.stringify(text)}`);\n\t}\n\treturn effort;\n}\n\n/**\n * Map an online level keyword to an {@link Effort}; earliest match wins.\n *\n * `max` is only offered to the classifier when the target model exposes that\n * tier, but it is always parsed: an unsupported `max` is snapped back down by\n * {@link clampAutoThinkingEffort} rather than failing the turn.\n */\nexport function parseDifficultyLevel(text: string): Effort | undefined {\n\tconst lower = text.toLowerCase();\n\tconst candidates: Array<[number, Effort]> = [];\n\t// `xhigh` must be probed as its own token: `\\bhigh\\b` cannot match the \"high\"\n\t// inside \"xhigh\" (no word boundary between `x` and `h`), so the two never collide.\n\tconst xhigh = lower.search(/x[\\s_-]?high/);\n\tif (xhigh >= 0) candidates.push([xhigh, Effort.XHigh]);","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/auto-thinking/classifier.ts#L158-L194","documentation":"The local auto-thinking classifier asks a tiny local model to label a prompt with a difficulty bucket (e.g. low/medium/high/max). classifyLocal parses the model's output with parseDifficultyBucket; if the returned text cannot be mapped to an Effort bucket, it throws this error naming the offending text.","triggerScenarios":"classifyLocal is called (via effort) with a local tiny-model key, tinyModelClient.complete returns non-empty text, but parseDifficultyBucket finds no valid bucket keyword in it — e.g. the model answers with reasoning, a full sentence, or a different label format.","commonSituations":"Local model outputs verbose prose instead of the expected single keyword; a reasoning-enabled tiny model burns tokens on thinking and answers oddly; the prompt was changed or the model is misconfigured so its reply format drifts.","solutions":["Inspect the JSON-embedded text in the message to see what the model actually returned","Retry the classification — local tiny models can produce flaky output","Verify the configured classifier model is the intended tiny local model and that reasoning output is not polluting the answer","If it reproduces consistently, check parseDifficultyBucket's accepted keywords against the local prompt's answer format"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const text = await tinyModelClient.complete(modelKey, builtPrompt, { maxTokens, signal });\nif (!text || !parseDifficultyBucket(text)) {\n  // fall back / retry before surfacing the error\n}","typeGuard":null,"tryCatchPattern":"try {\n  const effort = await effort(input);\n} catch (err) {\n  if (String((err as Error).message).includes(\"unparseable local classification\")) {\n    effort = DEFAULT_EFFORT; // or retry once\n  } else throw err;\n}","preventionTips":["Retry classification once on unparseable output before failing the turn","Keep maxTokens high enough that reasoning models finish within the budget","Use the prompt format the local model was validated against","Fall back to a default effort bucket instead of failing the whole task"],"tags":["classification","local-model","parsing"],"backgroundTag":"model-output-unparseable","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}