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

  1. Read the thrown message: if it is the server's error text, fix the issue it names (e.g. re-authenticate, fix payload)
  2. If the generic message shows 401/403, log in again / refresh credentials before syncing
  3. Re-run the sync — transient 5xx or truncated uploads are often resolved by retry
  4. 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

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


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)