{"record":{"id":"426fb726e6cf661c","repo":"paperclipai/paperclip","slug":"expected-99-captures-found-capturefiles-length","errorCode":null,"errorMessage":"Expected 99 captures, found ${captureFiles.length}","messagePattern":"Expected 99 captures, found (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ingest-app-definitions.mjs","lineNumber":1616,"sourceCode":"      ...(connectionMethod.extensionFields ?? []),\n      ...(connectionMethod.credentialFields ?? []),\n    ])\n      if (\n        connectionField.required &&\n        connectionField.type !== \"checkbox\" &&\n        !connectionField.placeholder\n      )\n        throw new Error(\n          `${app.slug}/${connectionMethod.key}/${connectionField.key}: required field needs placeholder`,\n        );\n  }\n};\nconst captureFiles = definitionsOnly ? [] : fs\n  .readdirSync(corpus)\n  .filter((fileName) => fileName.endsWith(\".md\") && fileName !== \"INDEX.md\")\n  .sort();\nif (!definitionsOnly && captureFiles.length !== 99)\n  throw new Error(`Expected 99 captures, found ${captureFiles.length}`);\nconst parsedCaptures = Object.fromEntries(\n  captureFiles.map((fileName) => [\n    path.basename(fileName, \".md\"),\n    parseCapture(fileName),\n  ]),\n);\nconst reviewReport = {\n  schemaVersion: 1,\n  corpusSize: captureFiles.length,\n  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)","sourceCodeStart":1598,"sourceCodeEnd":1634,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/ingest-app-definitions.mjs#L1598-L1634","documentation":"The ingestion script asserts an exact count of 99 corpus capture markdown files (all *.md except INDEX.md, sorted) before parsing. This is an intentional canary: the capture corpus is expected to be in lockstep with the 99 supported app definitions, so an off-by-one means a capture was added or removed without a corresponding definition change.","triggerScenarios":"Running scripts/ingest-app-definitions.mjs without --definitions-only when the number of .md files (excluding INDEX.md) in the corpus directory (PAPERCLIP_CONTENT_TEMPLATES or ../../paperclip-content/research/connections/vercel/templates) is not exactly 99.","commonSituations":"Adding a capture .md for a new app without updating the expected count constant; a stray .md (scratch notes, editor backup like app.md.bak won't match, but notes.md will) landing in the corpus directory; deleting a capture while removing an app; cloning a partial content repo; PAPERCLIP_CONTENT_TEMPLATES pointing at the wrong directory.","solutions":["Update the hardcoded expected count (99) in scripts/ingest-app-definitions.mjs to the new intended corpus size after intentionally adding/removing an app capture.","List the corpus directory and diff against expected app slugs to find stray or missing .md files; remove unrelated markdown or restore the missing capture.","Verify PAPERCLIP_CONTENT_TEMPLATES points at the correct templates directory.","Use --definitions-only if you only need to regenerate definitions without the capture corpus."],"exampleFix":"// before\nif (!definitionsOnly && captureFiles.length !== 99)\n  throw new Error(`Expected 99 captures, found ${captureFiles.length}`);\n// after (intentionally added a 100th app)\nif (!definitionsOnly && captureFiles.length !== 100)\n  throw new Error(`Expected 100 captures, found ${captureFiles.length}`);","handlingStrategy":"validation","validationCode":"const count = fs.readdirSync(corpus).filter(f => f.endsWith('.md') && f !== 'INDEX.md').length;\nif (count !== 99) console.warn(`Corpus count is ${count}; update the expected count or fix the corpus before ingesting.`);","typeGuard":null,"tryCatchPattern":"try { await ingest(); } catch (e) { if (/^Expected 99 captures/.test(e.message)) { console.error('Capture corpus out of sync: diff corpus dir against app slugs, or update the expected count.'); process.exitCode = 1; } else throw e; }","preventionTips":["Treat the expected-count constant as a lockstep contract: update it in the same PR that adds/removes an app.","Keep scratch markdown out of the corpus directory.","Pin PAPERCLIP_CONTENT_TEMPLATES in CI so the corpus source is deterministic."],"tags":["count-check","corpus","ingest","invariant"],"backgroundTag":"internal-invariant-violation","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"}