paperclipai/paperclip · error · Error

: no captured states

Error message

${fileName}: no captured states

What it means

parseCapture() reads a corpus capture markdown file and extracts connection-flow states via /^## State: (.+)$/gm headings. A capture file with zero such headings is unusable — it contains no replayable states — so ingestion rejects it outright to prevent emitting an empty interaction definition.

Solutions

  1. Edit the capture .md file so it contains at least one '## State: <Name>' heading, with the state's content below it.
  2. Compare with an existing valid capture file and match its heading format exactly ('## State: ' with two hashes and a colon-space).
  3. Check PAPERCLIP_CONTENT_TEMPLATES points at the intended templates directory; a wrong corpus dir pulls in non-capture markdown.
  4. If the file is a placeholder or index-like doc, delete it or rename it so it does not match the *.md corpus glob (or list it as INDEX.md-style excluded name).

Example fix

// before (capture.md)
# Slack connection
Some prose only.
// after
# Slack connection
## State: Initial
Describe the initial captured state here.
Defensive patterns

Strategy: validation

Validate before calling

const md = fs.readFileSync(file, 'utf8');
if (!/^## State: /m.test(md)) throw new Error(`${file}: missing '## State:' headings`);

Try / catch

try { const states = parseCapture(fileName); } catch (e) { if (e.message.endsWith('no captured states')) { console.error(`Capture ${fileName} has no '## State:' headings; fix the file format.`); process.exitCode = 1; } else throw e; }

Prevention

When it happens

Trigger: parseCapture(fileName) is called for each *.md file (except INDEX.md) in the captures corpus directory (PAPERCLIP_CONTENT_TEMPLATES or ../../paperclip-content/research/connections/vercel/templates) and the file content has no lines matching '^## State: '.

Common situations: A contributor added a new capture .md file that only has prose or a different heading convention (e.g. '### state:' or '# State:'); a file was copied but body content was never filled in; an editor or linter stripped/renamed the '## State:' headings; wrong corpus directory env var points at unrelated markdown files that happen to end in .md.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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

Appendix: source

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

];
for (const slug of reviewedGoogleSlugs) {
  const existingIndex = apps.findIndex((app) => app.slug === slug);
  if (existingIndex >= 0) apps.splice(existingIndex, 1);
  apps.push(
    JSON.parse(fs.readFileSync(path.join(out, `${slug}.json`), "utf8")),
  );
}
const parseTableRow = (line) =>
  line
    .slice(1, -1)
    .split("|")
    .map((cell) => cell.trim());
const parseCapture = (fileName) => {
  const markdown = fs.readFileSync(path.join(corpus, fileName), "utf8");
  const stateMatches = [...markdown.matchAll(/^## State: (.+)$/gm)];
  if (stateMatches.length === 0)
    throw new Error(`${fileName}: no captured states`);
  return stateMatches.map((match, index) => {
    const body = markdown.slice(
      match.index + match[0].length,
      stateMatches[index + 1]?.index ?? markdown.length,
    );
    const inputsBlock =
      body.match(/### Inputs\n([\s\S]*?)(?=\n### |$)/)?.[1] ?? "";
    const inputRows = inputsBlock
      .split("\n")
      .filter((line) => line.startsWith("|"))
      .slice(2)
      .map(parseTableRow);
    const fields = inputRows.map(
      ([label, tagType, required, placeholder, prefilledValue, checked]) => ({
        label,
        tagType,
        required: required.toLowerCase() === "yes",
        placeholder: placeholder || null,

View on GitHub (pinned to 3f1d897a7c)