JuliusBrussee/caveman · error · MiddlewareError
invalid_recovery
invalid_recovery
Error message
invalid_recovery
What it means
MiddlewareError 'invalid_recovery' is thrown by validatePage when a RecoveryPage fails a structural check: schema_version, handle matching the RetrieveArgs, token-shaped source_id, hash-shaped original_sha256, string text within maxBytes, integer total_bytes/offset matching the requested offset, allowed kind ('original_page'|'excerpt'), boolean complete, and next_offset either null or an integer. It is the first of several recovery-page integrity gates before text is returned to the caller.
Solutions
- Ensure the page's handle exactly equals args.handle and offset equals args.offset ?? 0
- Fix the source so kind is 'original_page' or 'excerpt' and next_offset is null or an integer
- Check total_bytes/offset are integers and text byte length does not exceed maxBytes
- Regenerate the page from storage instead of replaying a cached one
- Verify SDK/storage adapter versions agree on the RecoveryPage schema
Example fix
// before const page = await store.fetch(anyHandle, requestedOffset + 1); // after: echo back exactly the requested handle/offset const page = await store.fetch(args.handle, args.offset ?? 0);
Defensive patterns
Strategy: validation
Validate before calling
function precheckPage(p, args) {
return p && p.schema_version === 1 && p.handle === args.handle &&
typeof p.source_id === 'string' && typeof p.original_sha256 === 'string' &&
typeof p.text === 'string' && Number.isInteger(p.total_bytes) && Number.isInteger(p.offset) &&
p.offset === (args.offset ?? 0) &&
['original_page','excerpt'].includes(p.kind) &&
(p.next_offset === null || Number.isInteger(p.next_offset));
}
if (!precheckPage(page, args)) throw new Error('recovery page fails local precheck'); Type guard
const isRecoveryPage = (p) => !!p && p.schema_version === 1 && typeof p.handle === 'string' && typeof p.text === 'string' && (p.kind === 'original_page' || p.kind === 'excerpt') && typeof p.complete === 'boolean' && (p.next_offset === null || Number.isInteger(p.next_offset));
Try / catch
try { const page = await retrieve(args); } catch (e) {
if (e.code === 'invalid_recovery') {
// refetch from canonical storage; do not serve unvalidated recovery data
cache.invalidate(args.handle);
page = await retrieve(args);
} else throw e;
} Prevention
- Ensure storage adapters echo back the exact handle and offset from the request
- Compute all offsets/sizes in bytes, not characters
- Invalidate caches when handles or schema versions change
- Test adapters for first/middle/last-page boundaries before shipping
When it happens
Trigger: Calling validatePage (via retrieve) with a page whose handle differs from args.handle, offset not equal to args.offset ?? 0, kind outside the two allowed values, non-integer or negative total_bytes/offset, next_offset that is neither null nor an integer, or text larger than maxBytes.
Common situations: Pages returned from a cache keyed by an older handle, a storage adapter returning excerpt data with original_page metadata, byte-offset paging logic off by one so the returned offset differs from the request, or oversized pages after a maxBytes configuration change.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Configure exact POST paths and native LLM protocols
- invalid_configuration
- invalid_endpoint
- invalid_endpoint
- invalid_plan
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/020b240d84be44e4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/typescript/src/middleware/validate.ts:80
reduction += r.tokens_before - r.tokens_after;
if (r.unique_original) { unique += r.tokens_before - r.tokens_after; credited.add(r.original_sha256); }
}
for (const skipped of p.skipped) {
if (!segments.has(skipped.segment_id) || seen.has(skipped.segment_id) || !isToken(skipped.reason)) invalid();
seen.add(skipped.segment_id);
}
if (seen.size !== segments.size || m.tokens_before - m.tokens_after !== reduction || m.unique_tokens_reduced !== unique ||
(p.replacements.length > 0 && (p.status !== 'optimized' || reduction <= m.recovery_overhead_tokens)) ||
(p.status === 'optimized' && p.replacements.length === 0)) invalid();
return p;
}
export async function validatePage(value: unknown, args: RetrieveArgs, maxBytes: number): Promise<RecoveryPage> {
const p = value as RecoveryPage;
if (!p || p.schema_version !== 1 || p.handle !== args.handle || !isToken(p.source_id) || !isHash(p.original_sha256) ||
typeof p.text !== 'string' || byteLength(p.text) > maxBytes || !integer(p.total_bytes) || !integer(p.offset) || p.offset !== (args.offset ?? 0) ||
!['original_page','excerpt'].includes(p.kind) || typeof p.complete !== 'boolean' ||
!(p.next_offset === null || integer(p.next_offset))) throw new MiddlewareError('invalid_recovery');
const length = byteLength(p.text);
if (p.kind === 'original_page' && (p.offset + length > p.total_bytes ||
p.next_offset !== (p.offset + length < p.total_bytes ? p.offset + length : null) ||
p.complete !== (p.offset === 0 && length === p.total_bytes))) throw new MiddlewareError('invalid_recovery');
if (p.kind === 'excerpt' && (p.complete || !args.query || p.next_offset !== 0)) throw new MiddlewareError('invalid_recovery');
if (p.complete && await sha256(p.text) !== p.original_sha256) throw new MiddlewareError('invalid_recovery');
return p;
}
View on GitHub (pinned to 3ee70a1026)