{"record":{"id":"ac12025c781842f8","repo":"JuliusBrussee/caveman","slug":"result-error-message-practice-findings-sync-failed-response","errorCode":null,"errorMessage":"result.error?.message ?? `practice findings sync failed (${response.status})`","messagePattern":"result\\.error\\?\\.message \\?\\? `practice findings sync failed \\((.+?)\\)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":10468,"sourceCode":"    }));\n\n  const project = cfg.projectId ? `?project_id=${encodeURIComponent(cfg.projectId)}` : \"\";\n  const response = await fetch(`${cfg.baseURL}/api/v1/practice-findings${project}`, {\n    method: \"POST\",\n    headers: {\n      authorization: `Bearer ${cfg.token}`,\n      \"content-type\": \"application/json\",\n      \"x-cave-csrf\": \"cli\",\n    },\n    body: JSON.stringify({ findings }),\n  });\n  const result = (await response.json().catch(() => ({}))) as {\n    status?: string;\n    row_count?: number;\n    error?: { message?: string };\n  };\n  if (!response.ok || result.status !== \"completed\" || result.row_count !== findings.length) {\n    throw new Error(result.error?.message ?? `practice findings sync failed (${response.status})`);\n  }\n  return { kind: \"synced\", findings: findings.length };\n}\n\n// sync is the first-class verb: clear error when logged out, honest no-op when\n// there is nothing to send, one plain line when spans were uploaded.\nasync function sync() {\n  const cfg = await config();\n  requireAuth(cfg);\n  // These are independent evidence lanes. One corrupt/busy local store or one\n  // rejected practice POST must not starve the pending first-run aggregate;\n  // all lanes run, successful ones settle, then explicit sync reports any\n  // partial failure non-zero so the operator can retry it.\n  const [savingsLane, practicesLane, localScanLane] = await Promise.allSettled([\n    syncLocalSavings(cfg),\n    syncLocalPracticeFindings(cfg),\n    syncPendingLocalScan(cfg),\n  ] as const);","sourceCodeStart":10450,"sourceCodeEnd":10486,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L10450-L10486","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (intercepting at call site)\nawait syncPracticeFindings(findings);\n// after\ntry {\n  await syncPracticeFindings(findings);\n} catch (e) {\n  console.error(\"sync failed:\", (e as Error).message); // may carry server detail\n}","handlingStrategy":"try-catch","validationCode":"if (!Number.isFinite(findings.length) || findings.length === 0) throw new Error(\"nothing to sync\");\nif (!hasAuthCredentials()) await login();","typeGuard":"function isSyncResult(r: unknown): r is { status: string; row_count: number; error?: { message: string } } {\n  const o = r as any;\n  return typeof o?.status === \"string\" && typeof o?.row_count === \"number\";\n}","tryCatchPattern":"try {\n  await syncPracticeFindings(findings);\n} catch (e) {\n  const msg = (e as Error).message;\n  if (/\\(40[13]\\)/.test(msg)) await reloginAndRetry();\n  else if (/\\(5\\d\\d\\)/.test(msg)) await retryWithBackoff();\n  else console.error(\"Sync rejected by server:\", msg);\n}","preventionTips":["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"],"tags":["network","http","sync"],"backgroundTag":"http-error-response","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}