JuliusBrussee/caveman · error · Error
result.error?.message ?? `practice findings sync failed
Error message
result.error?.message ?? `practice findings sync failed (${response.status})` What it means
When syncing practice findings to the remote API, the CLI treats the sync as successful only if HTTP 2xx was returned, `status` equals "completed", and the reported `row_count` matches the number of findings sent. On any mismatch it throws, preferring the server-provided error message from the JSON body and falling back to a generic message that includes the HTTP status.
Solutions
- Read the thrown message: if it is the server's error text, fix the issue it names (e.g. re-authenticate, fix payload)
- If the generic message shows 401/403, log in again / refresh credentials before syncing
- Re-run the sync — transient 5xx or truncated uploads are often resolved by retry
- Check the API response schema: if `row_count` silently changed meaning, upgrade the CLI to match the current server contract
Example fix
// before (intercepting at call site)
await syncPracticeFindings(findings);
// after
try {
await syncPracticeFindings(findings);
} catch (e) {
console.error("sync failed:", (e as Error).message); // may carry server detail
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!Number.isFinite(findings.length) || findings.length === 0) throw new Error("nothing to sync");
if (!hasAuthCredentials()) await login(); Type guard
function isSyncResult(r: unknown): r is { status: string; row_count: number; error?: { message: string } } {
const o = r as any;
return typeof o?.status === "string" && typeof o?.row_count === "number";
} Try / catch
try {
await syncPracticeFindings(findings);
} catch (e) {
const msg = (e as Error).message;
if (/\(40[13]\)/.test(msg)) await reloginAndRetry();
else if (/\(5\d\d\)/.test(msg)) await retryWithBackoff();
else console.error("Sync rejected by server:", msg);
} Prevention
- Refresh auth tokens before long-running sync jobs
- Log the full HTTP response body on failure for diagnosis
- Retry idempotent syncs on 5xx with backoff
- Keep CLI and server API versions aligned
When it happens
Trigger: POSTing findings returns a non-2xx status, the response body's `status` field is not "completed", or `row_count` differs from findings.length — including empty/malformed bodies where the catch(() => ({})) fallback yields the generic `${status}` message.
Common situations: Server-side validation rejecting some findings, auth token expired causing 401/403, partial row ingestion due to server bugs, API version drift changing the response schema, or network proxies returning HTML error pages.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- awscreds: sts assume role with web identity failed
- binary download failed: HTTP
- cave_agent_tool_timeout | cave_network_error
- cave_request_failed
- cave request failed ( )
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/ac12025c781842f8.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:10468
}));
const project = cfg.projectId ? `?project_id=${encodeURIComponent(cfg.projectId)}` : "";
const response = await fetch(`${cfg.baseURL}/api/v1/practice-findings${project}`, {
method: "POST",
headers: {
authorization: `Bearer ${cfg.token}`,
"content-type": "application/json",
"x-cave-csrf": "cli",
},
body: JSON.stringify({ findings }),
});
const result = (await response.json().catch(() => ({}))) as {
status?: string;
row_count?: number;
error?: { message?: string };
};
if (!response.ok || result.status !== "completed" || result.row_count !== findings.length) {
throw new Error(result.error?.message ?? `practice findings sync failed (${response.status})`);
}
return { kind: "synced", findings: findings.length };
}
// sync is the first-class verb: clear error when logged out, honest no-op when
// there is nothing to send, one plain line when spans were uploaded.
async function sync() {
const cfg = await config();
requireAuth(cfg);
// These are independent evidence lanes. One corrupt/busy local store or one
// rejected practice POST must not starve the pending first-run aggregate;
// all lanes run, successful ones settle, then explicit sync reports any
// partial failure non-zero so the operator can retry it.
const [savingsLane, practicesLane, localScanLane] = await Promise.allSettled([
syncLocalSavings(cfg),
syncLocalPracticeFindings(cfg),
syncPendingLocalScan(cfg),
] as const);View on GitHub (pinned to 3ee70a1026)