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
- Edit the capture .md file so it contains at least one '## State: <Name>' heading, with the state's content below it.
- Compare with an existing valid capture file and match its heading format exactly ('## State: ' with two hashes and a colon-space).
- Check PAPERCLIP_CONTENT_TEMPLATES points at the intended templates directory; a wrong corpus dir pulls in non-capture markdown.
- 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
- Copy the template heading format ('## State: Name') from an existing capture when authoring new ones.
- Add a lint rule that flags .md files in the corpus lacking '## State:' headings.
- Never rename state headings without updating the parser regex.
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
- : capture has no states
- / / : required field needs placeholder
- / : oauth requires ownershipModes
- / : tool api_key requires keyPlacement
- : invalid AppDefinition
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)