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
- Retry with the exact handle copied from the <<ccr:HANDLE>> marker or the prior tool result.
- Check that the Caveman MCP server is running/connected and re-establish the session if needed.
- 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
- Copy handles verbatim from the tool result or <<ccr:HANDLE>> marker — never invent them.
- Treat handles as ephemeral; re-run the original query if retrieval errors persist.
- Monitor MCP server connectivity to catch upstream outages early.
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
- MCP changes require the ownership transaction
- : MCP ownership journal failed; native config may already…
- caveman-cloud MCP changed after setup; refusing destructive…
- caveman-cloud MCP postflight mismatch
- caveman-cloud MCP registration failed exact postflight
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)