JuliusBrussee/caveman · error

caveman_retrieve failed

Error message

caveman_retrieve failed

What it means

In packages/pi-extension/src/index.ts:154, the `caveman_retrieve` Pi tool calls `recovery.retrieve(handle, query, signal)`. If the underlying MCP retrieval returns `isError`, the tool throws an Error whose message is the returned text (or the generic 'caveman_retrieve failed'), so failures surface as real Pi tool errors rather than fabricated content fed back to the model.

Solutions

  1. Retry with the exact handle copied from the <<ccr:HANDLE>> marker or the prior tool result.
  2. Check that the Caveman MCP server is running/connected and re-establish the session if needed.
  3. Re-run the original work to mint a fresh handle if the old one is invalid or expired.

Example fix

// before
await retrieve({ recovery_handle: 'ccr_guess', query: '...' });
// after
await retrieve({ recovery_handle: 'ccr_abc123', query: '...' }); // handle from the marker
Defensive patterns

Strategy: try-catch

Validate before calling

function assertHandle(handle: string) {
  if (!/^ccr_/.test(handle)) throw new Error(`Invalid recovery handle format: ${handle}`);
}

Type guard

const isCcrHandle = (h: string): h is `ccr_${string}` => h.startsWith('ccr_');

Try / catch

try {
  const result = await recovery.retrieve(handle, query, signal);
  if (result.isError) throw new Error(result.text || 'caveman_retrieve failed');
} catch (e) {
  log(`caveman_retrieve failed for ${handle}: ${String(e)}`);
  // surface an honest error to the model; do not fabricate content
  throw e;
}

Prevention

When it happens

Trigger: Calling the caveman_retrieve Pi tool with an unknown/expired recovery_handle, a bad query, or while the upstream Caveman MCP server returns any error result.

Common situations: A model inventing a handle not issued by Caveman; the compressed context expired server-side; MCP server restart or connection loss mid-session.

Related errors


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

Appendix: source

Thrown at packages/pi-extension/src/index.ts:154

  const GUARD_turn_end = GUARD("turn_end");
  const GUARD_tool_call = GUARD("tool_call");
  const GUARD_tool_result = GUARD("tool_result");
  const GUARD_session_before_compact = GUARD("session_before_compact");
  const GUARD_session_compact = GUARD("session_compact");
  const GUARD_session_shutdown = GUARD("session_shutdown");

  pi.registerTool({
    name: RECOVERY_TOOL,
    label: "Retrieve compressed context",
    description: "Recover exact original content from a Caveman recovery handle.",
    parameters: Type.Object({
      recovery_handle: Type.String({ description: "Exact ccr_ handle returned by Caveman or copied from a <<ccr:HANDLE>> marker." }),
      query: Type.Optional(Type.String({ description: "One broad description covering every detail needed from this handle." })),
    }),
    async execute(_toolCallId, params, signal) {
      const result = await recovery.retrieve(params.recovery_handle, params.query, signal);
      // MCP errors surface as Pi tool errors (thrown), never as fabricated content.
      if (result.isError) throw new Error(result.text || "caveman_retrieve failed");
      return { content: [{ type: "text", text: result.text }], details: { recovery_handle: params.recovery_handle } };
    },
  });

  pi.on("session_start", GUARD_session_start(async (_event: unknown, ctx: ExtensionContext) => {
    router ??= new ProviderRouter(pi, (message, kind) => notify(ctx, message, kind));
    // A new/resumed/forked session replaces the runtime; reset everything.
    gateDone = false;
    coreContext = undefined;
    pendingContext = [];
    pendingBytes = 0;
    // A failed gate in a replacement session must not retain the old route.
    if (!(await router.closeGate(ctx))) {
      gateDone = true;
      return;
    }
    try {
      sessionId = ctx.sessionManager.getSessionId() || "default";

View on GitHub (pinned to 3ee70a1026)