paperclipai/paperclip · error · Error

Expected 99 captures, found

Error message

Expected 99 captures, found ${captureFiles.length}

What it means

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.

Solutions

  1. 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.
  2. 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.
  3. Verify PAPERCLIP_CONTENT_TEMPLATES points at the correct templates directory.
  4. Use --definitions-only if you only need to regenerate definitions without the capture corpus.

Example fix

// before
if (!definitionsOnly && captureFiles.length !== 99)
  throw new Error(`Expected 99 captures, found ${captureFiles.length}`);
// after (intentionally added a 100th app)
if (!definitionsOnly && captureFiles.length !== 100)
  throw new Error(`Expected 100 captures, found ${captureFiles.length}`);
Defensive patterns

Strategy: validation

Validate before calling

const count = fs.readdirSync(corpus).filter(f => f.endsWith('.md') && f !== 'INDEX.md').length;
if (count !== 99) console.warn(`Corpus count is ${count}; update the expected count or fix the corpus before ingesting.`);

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/426fb726e6cf661c. Report an issue: GitHub.

Appendix: source

Thrown at scripts/ingest-app-definitions.mjs:1616

      ...(connectionMethod.extensionFields ?? []),
      ...(connectionMethod.credentialFields ?? []),
    ])
      if (
        connectionField.required &&
        connectionField.type !== "checkbox" &&
        !connectionField.placeholder
      )
        throw new Error(
          `${app.slug}/${connectionMethod.key}/${connectionField.key}: required field needs placeholder`,
        );
  }
};
const captureFiles = definitionsOnly ? [] : fs
  .readdirSync(corpus)
  .filter((fileName) => fileName.endsWith(".md") && fileName !== "INDEX.md")
  .sort();
if (!definitionsOnly && captureFiles.length !== 99)
  throw new Error(`Expected 99 captures, found ${captureFiles.length}`);
const parsedCaptures = Object.fromEntries(
  captureFiles.map((fileName) => [
    path.basename(fileName, ".md"),
    parseCapture(fileName),
  ]),
);
const reviewReport = {
  schemaVersion: 1,
  corpusSize: captureFiles.length,
  providers: captureFiles.map((fileName) => {
    const slug = path.basename(fileName, ".md");
    const states = parsedCaptures[slug].map((state) => inferState(slug, state));
    return {
      slug,
      stateCount: states.length,
      states,
      ambiguities: states
        .filter((state) => !state.auth)

View on GitHub (pinned to 3f1d897a7c)