JuliusBrussee/caveman · error · CavemanRunError

cave_claude_terminal_${result?.subtype ?? "missing"}

cave_claude_terminal_${result?.subtype ?? "missing"}

Error message

the Claude Agent SDK ended with ${result?.subtype ?? "no result"}

What it means

runClaudeAgentWithOptions treats any Claude Agent SDK result that is missing, has subtype !== 'success', or has is_error=true as a terminal failure. It throws a carry() error with code `cave_claude_terminal_<subtype>` (or `_missing`) and this message naming the actual subtype.

Solutions

  1. Read the error code suffix for the actual subtype (e.g. error_max_turns) and raise maxTurns/permission settings if that is the cause
  2. Re-run with verbose logging to capture the SDK's failure detail (auth, model, tool errors)
  3. Verify Claude CLI authentication (`claude` runs standalone) and that the selected model name is valid
  4. Pin/upgrade the Claude Agent SDK so result shape matches what this runtime expects

Example fix

// before
runClaudeAgent({ prompt, maxTurns: 3 }) // hits error_max_turns
// after
runClaudeAgent({ prompt, maxTurns: 30, allowedTools: [...] })
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: confirm SDK auth and model before the run
if (!isSupportedClaudeModel(selected)) throw new Error(`unsupported model: ${selected}`)

Type guard

function isSDKSuccess(r: unknown): r is { subtype: 'success'; is_error: false } {
  return typeof r === 'object' && r !== null &&
    (r as any).subtype === 'success' && (r as any).is_error === false
}

Try / catch

try {
  await runClaudeAgent(opts)
} catch (err) {
  const m = /cave_claude_terminal_(.+)/.exec(err.code ?? '')
  if (m?.[1] === 'error_max_turns') { /* retry with higher maxTurns */ }
  else { /* surface auth/model failure */ }
}

Prevention

When it happens

Trigger: The SDK run finishes with subtype values like 'error_max_turns', 'error_during_execution', or returns undefined result; or returns success with is_error=true. Surfaced to callers of runClaudeAgent/runClaudeAgentInternal.

Common situations: Hitting the max-turns limit on long agentic tasks; the underlying Claude CLI crashing or being killed; invalid model name or auth failure causing the run to abort; SDK version mismatch changing the result shape.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/a5ce8e3c01457d3c. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/claude-runtime.ts:324

        cacheWriteTokens: usage.cacheWriteTokens,
        reasoningTokens: 0,
        estimatedUsd: usage.catalogCostUsd,
        unpriced: !usage.priced,
        usageBasis: "provider_reported",
        clampedOutputTokens: undefined,
      });
    }
    for (const name of toolCalls) receipt.recordToolCall(name);
    const carry = (code: string, message: string): CavemanRunError =>
      new CavemanRunError(code, message, receipt.build({
        runId: runID,
        agentId: definition.id,
        stopReason: "complete",
        meter: undefined,
      }));

    if (result === undefined || result.subtype !== "success" || result.is_error) {
      throw carry(
        `cave_claude_terminal_${result?.subtype ?? "missing"}`,
        `the Claude Agent SDK ended with ${result?.subtype ?? "no result"}`,
      );
    }
    if (assistantModel !== undefined && normalizeClaudeModel(assistantModel) !== selected) {
      throw carry(
        "cave_provider_model_identity_mismatch",
        `expected ${selected}, the SDK answered as ${assistantModel}`,
      );
    }
    const text = result.result;
    if (definition.output?.schema !== undefined) {
      let parsed: unknown;
      try {
        parsed = result.structured_output ?? JSON.parse(text);
      } catch {
        throw carry("cave_output_schema_invalid_json", "the SDK output was not valid JSON");
      }

View on GitHub (pinned to 3ee70a1026)