{"record":{"id":"c86ff84896b1c182","repo":"paperclipai/paperclip","slug":"app-slug-capture-has-no-states","errorCode":null,"errorMessage":"${app.slug}: capture has no states","messagePattern":"(.+?): capture has no states","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ingest-app-definitions.mjs","lineNumber":1644,"sourceCode":"  providers: captureFiles.map((fileName) => {\n    const slug = path.basename(fileName, \".md\");\n    const states = parsedCaptures[slug].map((state) => inferState(slug, state));\n    return {\n      slug,\n      stateCount: states.length,\n      states,\n      ambiguities: states\n        .filter((state) => !state.auth)\n        .map(\n          (state) => `Auth is not explicit in capture state: ${state.label}`,\n        ),\n    };\n  }),\n};\nfor (const app of apps) {\n  validateApp(app);\n  if (parsedCaptures[app.slug] && parsedCaptures[app.slug].length === 0)\n    throw new Error(`${app.slug}: capture has no states`);\n}\nfs.mkdirSync(out, { recursive: true });\nfor (const app of apps)\n  fs.writeFileSync(\n    path.join(out, `${app.slug}.json`),\n    JSON.stringify(app, null, 2) + \"\\n\",\n  );\nif (!definitionsOnly) fs.writeFileSync(\n  path.join(root, \"packages/shared/src/app-definitions.ingestion-report.json\"),\n  JSON.stringify(reviewReport, null, 2) + \"\\n\",\n);\nconst imports = apps\n  .map(\n    (a, i) =>\n      `import a${i} from \"./app-definitions/${a.slug}.json\" with { type: \"json\" };`,\n  )\n  .join(\"\\n\");\nfs.writeFileSync(","sourceCodeStart":1626,"sourceCodeEnd":1662,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/ingest-app-definitions.mjs#L1626-L1662","documentation":"After parsing, each app whose slug has a parsed capture must have at least one state. Because parseCapture() only returns entries derived from '## State:' headings, an empty array here means the capture matched the 99-count check but still yielded no states for this app — an inconsistency caught before writing output files.","triggerScenarios":"In the final loop over apps, for an app where parsedCaptures[app.slug] exists (the capture file <slug>.md was parsed) and its array length is 0. In practice parseCapture throws on zero headings, so this guards against a future parseCapture change returning an empty array (e.g. filtering states) or an empty-array assignment.","commonSituations":"Refactoring parseCapture to filter/skip states and returning [] for edge-case files; test doubles or patched ingestion code injecting an empty capture list; a slug whose capture file exists but whose states are all filtered out by a new criterion.","solutions":["Inspect the capture file for the app's slug and ensure at least one '## State:' section survives parsing.","Check recent changes to parseCapture() for filtering logic that can drop all states; fix the filter or re-add the states.","If the app should not have a capture at all, remove the capture file (and adjust the 99-count) rather than shipping an empty capture."],"exampleFix":"// before\nconst states = stateMatches.filter((m) => wantState(m[1])); // can be []\n// after\nif (states.length === 0) throw new Error(`${fileName}: no captured states`);","handlingStrategy":"validation","validationCode":"for (const app of apps) {\n  const cap = parsedCaptures[app.slug];\n  if (cap && cap.length === 0) throw new Error(`${app.slug}: capture parsed to zero states`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep parseCapture() fail-fast (throw on zero headings) rather than returning [].","Review any new state-filtering logic in parseCapture for empty-result edge cases.","Add a unit test that every parsed capture yields >= 1 state."],"tags":["validation","capture","ingest","invariant"],"backgroundTag":"empty-result-set","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}