JuliusBrussee/caveman · error · Error
result.error?.message ?? `sync import failed
Error message
result.error?.message ?? `sync import failed (${response.status})` What it means
Thrown when the sync import POST completes but the server does not report a completed import: either `response.ok` is false or the parsed body's `status` is not "completed". If the server supplied `error.message`, that message is surfaced instead of the generic HTTP-status fallback.
Solutions
- Read the surfaced `error.message` for the server-side rejection reason.
- For 401 responses, re-login (`caveman login`) and retry the sync.
- Upgrade/align CLI and gateway versions so the import payload and status contract match.
- The sync watermark is only advanced after success, so simply retrying after fixing the cause re-sends the same rows safely.
Defensive patterns
Strategy: retry
Validate before calling
// Confirm auth and payload shape before syncing
const res = await fetch(`${baseURL}/healthz`);
if (!res.ok) throw new Error("gateway unhealthy — sync would fail"); Type guard
function isCompletedImport(v: unknown): v is { status: "completed" } {
return (v as { status?: unknown })?.status === "completed";
} Try / catch
try {
await syncImport();
} catch (e) {
console.error(`Sync import failed: ${e.message}`); // server error.message takes precedence
// watermark is not advanced on failure, so a plain retry is safe
await sleep(5000);
await syncImport();
} Prevention
- Re-login when the access token may have expired (401 is a common cause).
- Keep CLI/gateway payload schema aligned before bulk imports.
- Rely on the watermark: failed syncs re-send the same rows on retry, so just retry.
When it happens
Trigger: `caveman sync` when the import endpoint rejects the batch (validation, auth, 5xx) or returns a body with status other than "completed" (e.g. "partial", "failed").
Common situations: Spend-store rows the server refuses (schema drift between CLI and gateway); expired access token (401); gateway returning partial-completed statuses; proxy interference mangling the response.
Related errors
- result.error?.message ?? `local scan sync failed
- cave_request_failed
- cave request failed ( )
- caveman: remote gateway key revocation failed
- caveman: remote session revocation failed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/641c55ced390796e.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:10375
const ts = typeof r.ts === "string" ? r.ts : "";
// The proxy writes ClickHouse-layout UTC timestamps ("YYYY-MM-DD HH:MM:SS.mmm").
const ms = Date.parse(ts.includes("T") ? ts : ts.replace(" ", "T") + "Z");
if (!Number.isFinite(ms)) return [];
return directiveInstalls.filter((d) => ms >= d.installedAtMs).map((d) => d.id);
};
const body = rows.map((r) => syncRequestSpan(r, rowDirectives(r))).join("\n") + "\n";
const response = await fetch(`${cfg.baseURL}/api/v1/imports?format=caveman-jsonl`, {
method: "POST",
headers: {
authorization: `Bearer ${cfg.token}`,
"content-type": "application/octet-stream",
"x-cave-csrf": "cli",
},
body,
});
const result = (await response.json().catch(() => ({}))) as { status?: string; error?: { message?: string } };
if (!response.ok || result.status !== "completed") {
throw new Error(result.error?.message ?? `sync import failed (${response.status})`);
}
const num = (v: unknown) => (typeof v === "bigint" ? Number(v) : typeof v === "number" && Number.isFinite(v) ? v : 0);
const maxId = rows.reduce((m, r) => Math.max(m, num(r.id)), since);
writeSyncWatermark(key, maxId);
const tokensSaved = rows.reduce((sum, r) => sum + Math.max(0, num(r.compression_tokens_before) - num(r.compression_tokens_after)), 0);
const tokenBases = new Set(rows.map((r) => (typeof r.compression_token_count_basis === "string" ? r.compression_token_count_basis : "")).filter(Boolean));
const tokenCountBasis = tokenBases.size === 0 ? "unavailable" : tokenBases.size === 1 ? [...tokenBases][0] ?? "unavailable" : "mixed";
const cachedInputTokens = rows.reduce((sum, r) => sum + Math.max(0, num(r.cached_input_tokens)), 0);
const cacheCreationInputTokens = rows.reduce((sum, r) => sum + Math.max(0, num(r.cache_creation_input_tokens)), 0);
const headlineCompressionRefused = cacheCreationInputTokens > cachedInputTokens;
const savingsUSD = rows.reduce((sum, r) => sum + num(r.savings_usd), 0);
return {
kind: "synced",
spans: rows.length,
tokensSaved,
tokenCountBasis,
cachedInputTokens,View on GitHub (pinned to 3ee70a1026)