{"record":{"id":"635f51b2b9d47d16","repo":"musistudio/claude-code-router","slug":"resolve-retrieval-llm-did-not-complete-an-ast-plan","errorCode":null,"errorMessage":"Resolve retrieval LLM did not complete an AST planning round.","messagePattern":"Resolve retrieval LLM did not complete an AST planning round\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/mcp/toolhub-mcp.ts","lineNumber":1624,"sourceCode":"      if (refinementFeedback) {\n        messages.push(responseMessage);\n        messages.push({ role: \"user\", content: refinementFeedback });\n        continue;\n      }\n      break;\n    }\n\n    const selectedToolNames = uniqueStrings([...latestResolvedFromAnalyzer, ...llmSelectedNames]).slice(0, topK);\n    if (selectedToolNames.length === 0) {\n      throw new Error(didCallAnalyzer || analyzerCallCount > 0\n        ? \"Resolve retrieval did not converge on any valid catalog tools after AST refinement.\"\n        : \"Resolve retrieval did not converge on any valid catalog tools.\");\n    }\n    if (!didCallAnalyzer || analyzerCallCount === 0) {\n      referencedTokens = uniqueStrings([...referencedTokens, ...selectedToolNames]);\n    }\n    if (didCallAnalyzer && analyzerCallCount === 0) {\n      throw new Error(\"Resolve retrieval LLM did not complete an AST planning round.\");\n    }\n    if (!summary) {\n      summary = didCallAnalyzer\n        ? \"Resolved a planned end-to-end tool bundle with AST-assisted retrieval.\"\n        : \"Resolved a planned end-to-end tool bundle from the resolver model response.\";\n    } else if (summary.toLowerCase().includes(\"no strong tool bundle match was found\")) {\n      summary = \"Resolved a candidate tool bundle after iterative AST refinement.\";\n    }\n    return {\n      plannedSteps: plannedSteps.length > 0 ? plannedSteps : undefined,\n      referencedTokens,\n      selectedToolNames,\n      summary,\n      workflowSketch: workflowSketch || undefined\n    };\n  }\n\n  private async callOpenAiWithTools(","sourceCodeStart":1606,"sourceCodeEnd":1642,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/mcp/toolhub-mcp.ts#L1606-L1642","documentation":"Thrown when the resolve run did call into the analyzer pathway but the recorded analyzer call count is zero — an internal consistency check meaning the LLM was expected to complete an AST planning round and never did. It indicates a broken planner/analyzer integration rather than a bad query.","triggerScenarios":"Internal state mismatch: didCallAnalyzer is true while analyzerCallCount === 0, typically only reachable due to a bug in the resolve loop or a custom analyzer hook that doesn't increment the counter.","commonSituations":"Custom/injected analyzer implementations that bypass the built-in counting; library upgrades changing the analyzer contract; rarely, an edge case in the resolve state machine.","solutions":["If using a custom analyzer hook, ensure it routes through the library's analyzer invocation so the call count is tracked","Upgrade/downgrade to a library version where analyzer and resolve loop agree","Report as a bug with the query, model, and catalog snapshot if using stock configuration"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await toolhub.resolve({ query });\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"AST planning round\")) {\n    // internal invariant bug: capture diagnostics and report upstream\n    captureException(e, { query, model });\n    return fallbackSearch(query);\n  }\n  throw e;\n}","preventionTips":["Pin the library version in production","Report repro (query + model + catalog) to maintainers"],"tags":["toolhub","internal","invariant","analyzer"],"backgroundTag":"internal-invariant-violation","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}