JuliusBrussee/caveman · error · TypeError
Recovery requires its scoped handle
Error message
Recovery requires its scoped handle
What it means
The recovery tool executed via MCP requires the caller's model/tool-call to pass a `handle` string argument — the scoped handle issued by Caveman (e.g. a ccr_ handle). The execute callback in packages/middleware/typescript/src/mcp.ts:37 throws this TypeError when the tool arguments omit `handle` or pass a non-string value, because the underlying binding cannot resolve compressed context without it.
Solutions
- Ensure the tool inputSchema marks `handle` (recovery_handle) as required so the model and host enforce it.
- Validate/extract the handle in your tool-call handler before invoking the recovery tool.
- If the handle is missing at runtime, re-issue retrieval with a fresh scope so a new handle is generated.
Example fix
// before
await recoveryTool.execute({ query: 'summarize' });
// after
await recoveryTool.execute({ handle: 'ccr_abc123', query: 'summarize' }); Defensive patterns
Strategy: validation
Validate before calling
function hasHandle(args: unknown): args is { handle: string; [k: string]: unknown } {
return typeof args === 'object' && args !== null && typeof (args as any).handle === 'string' && (args as any).handle.length > 0;
} Type guard
const isRetrieveArgs = (a: unknown): a is { handle: string; query?: string } =>
typeof a === 'object' && a !== null && typeof (a as any).handle === 'string'; Try / catch
try {
const page = await recovery.execute(args, { signal });
} catch (e) {
if (e instanceof TypeError && /scoped handle/.test(e.message)) {
return { isError: true, content: [{ type: 'text', text: 'A recovery handle is required; use the ccr_ handle from the prior result.' }] };
}
throw e;
} Prevention
- Keep the tool inputSchema with handle marked required so hosts and models enforce it.
- Surface handle usage instructions in the tool description so models pass it correctly.
- Log tool-call arguments in dev to catch models omitting the handle.
When it happens
Trigger: An LLM tool call to the recovery tool with arguments lacking `handle` (or containing a non-string, e.g. a number or object), passed into `this.binding.execute`.
Common situations: A model hallucinating tool arguments and skipping the handle; a host schema not enforcing the required `recovery_handle`/`handle` property; hand-crafted tool invocation in tests.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- artifact_id is required
- assemble requires model and sessionId
- cave_tool_search_query_required
- cursor has unknown key(s)
- cursor must be an object
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/aa07f43f67186758.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/typescript/src/mcp.ts:37
/** Native host views only. No MCP framing, server or inference loop is added. */
export class CavemanMCPHost {
readonly recovery: MCPToolBinding;
private readonly binding;
private readonly adapter;
private readonly recoveryExecutor;
private readonly recoveryDefinition;
private readonly versionSupported;
constructor(private readonly options: MCPHostOptions) {
if (!options.serverId || !options.protocolVersion) throw new TypeError('Provide the native server identity and negotiated MCP protocol version');
this.versionSupported = adapterCompatible('mcp');
if (!this.versionSupported && options.runtime.mode !== 'off') options.runtime.decline('unsupported_version');
this.binding = options.runtime.recovery(options.scope);
this.adapter = { id: 'mcp', version: '0.1.0', framework_version: frameworkVersion('@modelcontextprotocol/sdk') ?? 'unknown', serialization_revision: `mcp-native-${options.protocolVersion}-v1` };
this.recovery = {
tool: { name: this.binding.name, description: this.binding.description, inputSchema: structuredClone(this.binding.inputSchema) as Tool['inputSchema'] },
execute: async (arguments_, options) => {
if (typeof arguments_.handle !== 'string') throw new TypeError('Recovery requires its scoped handle');
const page = await this.binding.execute({ ...arguments_, handle: arguments_.handle }, options?.signal ? { signal: options.signal } : undefined);
return { content: [{ type: 'text', text: JSON.stringify(page) }] };
},
};
this.recoveryExecutor = this.recovery.execute;
this.recoveryDefinition = JSON.stringify(this.recovery.tool);
}
register(tools: readonly MCPToolBinding[]): MCPToolBinding[] {
return !this.versionSupported || this.options.runtime.mode !== 'compress' || tools.some(item => item.tool.name === this.recovery.tool.name) ? [...tools] : [...tools, this.recovery];
}
/** Persist the original result; pass only this copied view to the model.
* The host supplies its original append-only context manifest across resumes.
* Final provider serialization and dispatch are outside this result boundary. */
async projectResult(result: CallToolResult, options: {
tool: Tool; callId: string; contextManifest: readonly ManifestItem[];
registeredTools?: readonly MCPToolBinding[]; sequence?: number; signal?: AbortSignal;View on GitHub (pinned to 3ee70a1026)