thedotmack/claude-mem · error · Error
projection timeout must be a positive safe integer strictly…
Error message
projection timeout must be a positive safe integer strictly shorter than the Hub lease
What it means
fetchProjectionWithTimeout validates its timeoutMs argument (default PROJECTION_FETCH_TIMEOUT_MS) before starting the upstream projection fetch. The parameter must be a positive safe integer strictly below PROJECTION_LEASE_MS, because the timeout is what aborts the fetch and its body read within the Hub's fencing-lease window. The error fires only when a caller (e.g. drainProjection wiring, or a test passing a custom timeout) supplies a non-integer, zero/negative, non-safe-integer, or lease-exceeding timeout — an invalid input defect, not a runtime fetch failure.
Solutions
- Pass a positive safe integer timeoutMs that is strictly less than PROJECTION_LEASE_MS
- Omit the parameter to use the PROJECTION_FETCH_TIMEOUT_MS default, which is already validated at module load
- Clamp or validate caller-supplied timeout values before invoking fetchProjectionWithTimeout
- Fix test harnesses that inject timeout values exceeding the lease
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at workers/sync-hub/src/index.ts:558 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/a9b563e95928e433.
Report an issue: GitHub.
Appendix: source
Thrown at workers/sync-hub/src/index.ts:558
status: number;
bodyText: string;
}
export type ProjectionFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
/**
* Bound both the upstream fetch and consumption of its response body. A fetch
* that returns headers but stalls the body must not outlive the fencing lease.
*/
export async function fetchProjectionWithTimeout(
url: string,
requestBody: string,
secret: string,
timeoutMs = PROJECTION_FETCH_TIMEOUT_MS,
fetchImpl: ProjectionFetch = fetch,
): Promise<ProjectionFetchResult> {
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs >= PROJECTION_LEASE_MS) {
throw new Error("projection timeout must be a positive safe integer strictly shorter than the Hub lease");
}
const controller = new AbortController();
let timeout: ReturnType<typeof setTimeout> | undefined;
const deadline = new Promise<never>((_resolve, reject) => {
timeout = setTimeout(() => {
controller.abort("projection fetch deadline exceeded");
reject(new Error("projection_upstream_timeout"));
}, timeoutMs);
});
const request = (async (): Promise<ProjectionFetchResult> => {
const response = await fetchImpl(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${secret}`,
},
body: requestBody,
signal: controller.signal,View on GitHub (pinned to d8bc9755e7)