thedotmack/claude-mem · error · Error
projection fetch timeout must be strictly shorter than the…
Error message
projection fetch timeout must be strictly shorter than the Hub lease
What it means
This is a startup configuration invariant in the sync-hub Worker module scope. The projection fetch timeout (PROJECTION_FETCH_TIMEOUT_MS, 45s) is asserted to be strictly less than the Hub fencing lease (PROJECTION_LEASE_MS, 90s), so an aborted upstream fetch always completes before the lease the Hub holds expires. If the constants are edited so the timeout meets or exceeds the lease, a timed-out fetch could overlap a new lease holder, breaking fencing; the module deliberately refuses to load by throwing at import time rather than allowing that race in production.
Solutions
- Lower PROJECTION_FETCH_TIMEOUT_MS so it stays strictly below PROJECTION_LEASE_MS, preserving the documented 45s < 60s < 90s ordering
- Raise PROJECTION_LEASE_MS if the fetch genuinely needs more time, keeping a safe margin below the Worker platform ceiling
- Add a test or CI check asserting the ordering of the three timing constants before deployment
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at workers/sync-hub/src/index.ts:95 when the library encounters an invalid state.
Common situations: See trigger scenarios.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/3415b687568a63f6.
Report an issue: GitHub.
Appendix: source
Thrown at workers/sync-hub/src/index.ts:95
* serialization overhead), and a per-op body that passes DO validation can
* never hit the SQLite row limit. Oversize requests get a deliberate 413 —
* never a generic 500 that clients would retry forever.
*/
const MAX_OPS_PER_PUSH = 500; // mirrors the getChanges page cap
const MAX_PUSH_BODY_BYTES = 8_000_000;
const CANONICAL_DECIMAL = /^(?:0|[1-9][0-9]*)$/;
// Scheduled repair is deliberately incremental. One page is at most 100 ops or
// 4 MB, so a deeply lagging user cannot monopolize a cron request or keep the
// per-user projection lease busy while every other user waits.
const REPAIR_DRAIN_MAX_PAGES = 1;
/**
* Pro declares a 60-second maximum duration. Abort the complete response-body
* read at 45 seconds while the Hub holds a 90-second fencing lease:
* Hub abort (45s) < Pro platform ceiling (60s) < Hub lease (90s).
*/
if (PROJECTION_FETCH_TIMEOUT_MS >= PROJECTION_LEASE_MS) {
throw new Error("projection fetch timeout must be strictly shorter than the Hub lease");
}
const encoder = new TextEncoder();
function json(status: number, data: unknown): Response {
return new Response(JSON.stringify(data), {
status,
headers: { "Content-Type": "application/json" },
});
}
function errorResponse(status: number, error: string): Response {
return json(status, { error });
}
interface AuthOk {
ok: true;
userId: string;View on GitHub (pinned to d8bc9755e7)