{"record":{"id":"a86996468ce1baea","repo":"JuliusBrussee/caveman","slug":"local-learn-snapshot-has-no-sinks-array-at-snaps","errorCode":null,"errorMessage":"local learn snapshot has no sinks array at ${snapshotPath}","messagePattern":"local learn snapshot has no sinks array at (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":10062,"sourceCode":"  return `${subject} · ${out.tokensSaved} estimated tokens saved (inferred; counter basis ${out.tokenCountBasis})${destination}`;\n}\n\n// syncLocalPracticeFindings moves only stable ids + non-negative token rates\n// from the current learn snapshot. Titles, suggestions, evidence, file paths,\n// and payload text never leave the machine. Empty snapshots are sent so stale\n// rows for this user are removed server-side.\nasync function syncLocalPracticeFindings(cfg: Config): Promise<PracticeSyncOutcome> {\n  const snapshotPath = join(cavemanHome(), \"reports\", \"caveman-learn.json\");\n  let raw: Record<string, unknown>;\n  try {\n    raw = JSON.parse(readFileSync(snapshotPath, \"utf8\")) as Record<string, unknown>;\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { kind: \"no_snapshot\" };\n    throw new Error(`local learn snapshot is unreadable at ${snapshotPath}`);\n  }\n\n  if (!Array.isArray(raw.sinks)) {\n    throw new Error(`local learn snapshot has no sinks array at ${snapshotPath}`);\n  }\n  const observed = typeof raw.generated_at === \"string\" ? new Date(raw.generated_at) : null;\n  if (!observed || Number.isNaN(observed.valueOf())) {\n    throw new Error(`local learn snapshot has no valid generated_at at ${snapshotPath}`);\n  }\n  const sessions = typeof raw.sessions_scanned === \"number\" && Number.isFinite(raw.sessions_scanned)\n    ? Math.max(0, Math.trunc(raw.sessions_scanned))\n    : 0;\n  const counter = (value: unknown) =>\n    typeof value === \"number\" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;\n\n  const findings = raw.sinks\n    .filter((sink): sink is Record<string, unknown> =>\n      !!sink && typeof sink === \"object\" && !Array.isArray(sink))\n    .filter((sink) => typeof sink.practice_id === \"string\" && sink.practice_id.length > 0)\n    .map((sink) => ({\n      sink_id: typeof sink.sink_id === \"string\" ? sink.sink_id : \"\",\n      practice_id: sink.practice_id as string,","sourceCodeStart":10044,"sourceCodeEnd":10080,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L10044-L10080","documentation":"The learn snapshot JSON parsed successfully but lacks the expected sinks array. The sync protocol requires a sinks list (empty snapshots are sent to clear stale rows), so a malformed snapshot is rejected.","triggerScenarios":"Thrown at packages/cli/src/index.ts:9217 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Regenerate the snapshot with the current `caveman learn` version","Verify no hand edits removed the sinks field from caveman-learn.json"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}