pipo. docs

Diagnostics

Every finding pipo check reports, by code. Each one is printed as file:line:col severity code message, followed by a hint that says what to do:

people-intake.pipo:61:10  error  P031  delivered.check 'file_exists' is not supported by output 'sqlite'
  supported: ack, record_exists, row_count, query, external, none

An error stops pipo start, pipo run, a live change and a proposal. A warning is reported but doesn't stop anything. pipo check --json gives the same findings as data. See The .pipo file.

CodeSeverityWhat it means
P001errorYAML syntax error
P002errorThe file doesn't match the schema
P010errorUnknown from target
P011errorBad route branch
P012errorFunction module or export missing
P013errorUndeclared secret
P014errorReferenced file missing
P015errorReserved node id
P016errorRoute used without a branch
P017errorelse isn't the last route branch
P020errorUnreachable node or output
P021errorDead end
P022errorUndeclared cycle
P023errorLoop target isn't upstream
P024errorLoop target unknown
P025warningUnused route branch
P026warningFan-out copies share an explicit key
P030errorUnknown action or provider
P031errorDelivery check not supported by the output
P032errorBatching not supported by the output
P033errorthen not allowed here
P034errorHanding to the agent without agent control
P035errorrespond outside an http input
P036errorNode needs exactly one kind
P037errorsqlite key not in columns
P038errorBad schedule
P039warningTwo pipelines share a listen port
P040errorExpression syntax error
P041errorVariable not available here
P050warningLiteral credential
P051errorSecrets outside with:
P052errorUntrusted code
P053warningAgent node without a budget
P054errorSchema file isn't a valid JSON Schema
P055errorBad agent.verify
P056warningTwo pipelines poll one telegram bot
P057errorTelegram send without chat_id
P058warningexec command holds spaces
P059errorfn module can't run in the runner
P060errorNot exactly one of input and inputs, or too many inputs
P061errorInput name clashes
P062errorTwo inputs clash
P063errorBad chain link
P064warningChain declared at one end only, or a chain cycle
P065errorselect without parse: html
P066errorNot an http or https URL

P001#

YAML syntax error (error)

The file isn't well-formed YAML: bad indentation, an unclosed quote or bracket, a tab, or a key repeated in one mapping. Nothing else is checked until the YAML parses.

Fix: Fix the YAML at the reported line and column. Quote values that contain : or start with {, [, * or &.

P002#

The file doesn't match the schema (error)

A structural problem the JSON Schema catches: an unknown key (with a did you mean hint), a missing required key, a value outside an enum, a wrong type, a duration that isn't like 500ms, 10s, 5m, 1h or 7d, or a name that doesn't match its pattern. Each connector's with: is checked against that connector's own schema. Semantic checks run only once the file has no P002.

Fix: Follow the hint. pipo schema prints the full schema, and the Connectors page lists every with: field.

P010#

Unknown from target (error)

A node's or the output's from names something that isn't an input or a node, or has more than one . (only route.branch is allowed).

Fix: Use an existing node id or input name (the hint suggests the closest one).

P011#

Bad route branch (error)

from: x.branch where x isn't a route node, or the route has no branch with that name.

Fix: Take packets from one of the route's branches, as listed in the hint, or drop the .branch part for a non-route node.

P012#

Function module or export missing (error)

A tap or transform uses fn.<name> but the file has no top-level fn: module, or the module doesn't export a function called <name>.

Fix: Add fn: ./<module>.ts, or export the function from it (the hint lists the module's exports).

P013#

Undeclared secret (error)

A with: block uses ${secrets.<name>} but <name> isn't declared under the top-level secrets: map.

Fix: Declare it: secrets: { <name>: env:MY_VAR } (or an op:// reference).

P014#

Referenced file missing (error)

The fn module, an input's schema or an agent node's with.schema names a file that doesn't exist. Paths are relative to the .pipo file; templated paths aren't checked.

Fix: Create the file or fix the path. In the builder, a missing schema file can be created with one click.

P015#

Reserved node id (error)

A node is called input or output. Those names refer to the pipeline's ends.

Fix: Rename the node.

P016#

Route used without a branch (error)

from: names a route node directly. Packets leave a route through one of its branches.

Fix: Use from: <route>.<branch> (the hint lists them).

P017#

else isn't the last route branch (error)

Branches are tried in order, so an else branch anywhere but last would hide the ones after it.

Fix: Move the else branch to the end of the route.

P020#

Unreachable node or output (error)

A node, or the output, can't be reached by following from links from any input.

Fix: Connect it: set its from to an input or a node that is reached, or remove it.

P021#

Dead end (error)

A node never leads to the output, or (with inputs:) an input that nothing takes packets from on the way to the output. Every packet must be able to end at the output.

Fix: Take packets from it in a later node or in output.from, or remove it.

P022#

Undeclared cycle (error)

The from links form a cycle. The graph may loop only through a bounded loop: { back_to, until, max }.

Fix: Break the cycle and use loop.back_to on the later node instead.

P023#

Loop target isn't upstream (error)

loop.back_to names a node that doesn't feed this one, directly or through other nodes.

Fix: Point back_to at this node itself or at a node before it.

P024#

Loop target unknown (error)

loop.back_to names something that isn't a node.

Fix: Use the id of an upstream node.

P025#

Unused route branch (warning)

No node and not the output takes packets from a route branch, so packets sent there are dropped as filtered.

Fix: Consume the branch (from: <route>.<branch>), or remove it if dropping is what you want.

P026#

Fan-out copies share an explicit key (warning)

A step fans out to more than one path that reaches the output, so the output writes one copy per branch. Each copy has the same meta.packet_id; only the default key (packet_id:<branch>) tells them apart. Fires when the sqlite key column, an http Idempotency-Key header, or a delivered.with lookup uses meta.packet_id without meta.branch.

Fix: Add meta.branch to the key, e.g. "${meta.packet_id}:${meta.branch}", or rely on the default key.

P030#

Unknown action or provider (error)

A tap, transform or agent value isn't a built-in connector or provider (or fn.<name> for taps and transforms). agent: claude is reported with a hint: the API provider is claude_api.

Fix: Use one of the names in the hint; see Connectors.

P031#

Delivery check not supported by the output (error)

delivered.check isn't one the output connector supports, e.g. file_exists on to: sqlite, or downstream on anything but to: pipeline.

Fix: Pick a check from the hint's list; the matrix is on the Connectors page.

P032#

Batching not supported by the output (error)

output.batch is set on an output that can't batch. Only sqlite and file batch.

Fix: Remove batch, or write to an output that supports it.

P033#

then not allowed here (error)

then: continue is set anywhere but on a tap node's on_error (a failed step that changes data can't be skipped); then: drop is used as the default errors.then, in output or in delivered.on_fail; or an input's on_invalid has a then other than agent (invalid input is always rejected, so it can't be dead-lettered, dropped, paused or halted).

Fix: Use dead_letter, pause, halt or (with agent control) agent there. In an input's on_invalid, remove then, or use then: agent to also tell the agent.

P034#

Handing to the agent without agent control (error)

then: agent, delivered.stall.then: agent or agent.on_stall: handle is used, but agent.control isn't true, so there would be no agent endpoint to hand the packet to.

Fix: Add agent: { control: true }, or choose another policy (dead_letter, pause, notify).

P035#

respond outside an http input (error)

on_invalid.respond (the HTTP status for a rejected request) is set on an input that isn't via: http.

Fix: Remove respond, or make the input via: http.

P036#

Node needs exactly one kind (error)

A node declares none of tap, transform, filter, route and agent, or more than one of them.

Fix: Give it one kind; split a node with two kinds into two nodes chained with from:.

P037#

sqlite key not in columns (error)

output.with.columns is set but doesn't include the key column (key, default packet_id), which is how a repeated write is recognised.

Fix: Add the key column to columns, or set key: to one of your columns.

P038#

Bad schedule (error)

A schedule, poll or crawl input has both or neither of cron and every, or its cron doesn't parse. Cron has 5 fields (minute, hour, day of month, month, day of week) and runs in UTC.

Fix: Use exactly one, e.g. every: 5m or cron: '0 9 * * 1-5'.

P039#

Two pipelines share a listen port (warning)

In one multi-file pipo check (a folder or several files), two pipelines' http inputs declare the same with.listen port. Only one of them can bind it. A single-file check never warns.

Fix: Give each pipeline its own port, or drop listen and reach them through the engine gateway (/in/<pipeline>).

P040#

Expression syntax error (error)

An expression (validate, filter, route, loop.until, lifetime.until) or a ${…} template doesn't parse, or uses syntax outside the allowed subset (assignment, arrow functions, new, method calls…). Also an unterminated ${.

Fix: Fix the expression; see Expressions and templates. Write $${ for a literal ${.

P041#

Variable not available here (error)

An expression uses a context variable that doesn't exist at that position, e.g. result outside delivered, stats outside lifetime.until and stall messages, or error outside a message.

Fix: Use one of the variables the hint lists.

P050#

Literal credential (warning)

A string in a with: block looks like a credential: its key names a token, secret, password, API key, authorization or credential and its value isn't an op:///env: reference or a template, or the value itself looks like one (Bearer …, sk-…, ghp_…, xox…-, AKIA…).

Fix: Declare it under secrets: (op://… or env:…) and use ${secrets.<name>}.

P051#

Secrets outside with: (error)

secrets is used in an expression or message, where its value could leak into data, logs or responses.

Fix: Use ${secrets.<name>} only inside with: blocks.

P052#

Untrusted code (error)

The fn module, or a pipeline file with exec nodes or a command input, came from a template you didn't write and that isn't trusted, or it was edited since it was trusted.

Fix: Read it, then run pipo trust <template> (or pipo trust <project-folder> after your own edit).

P053#

Agent node without a budget (warning)

The pipeline has an API agent node (claude_api) but no agent_budget. CLI agents (claude_code, codex, pi, opencode) don't trigger it.

Fix: Add agent_budget: { per_day: 1 } (the builder offers this as a one-click fix).

P054#

Schema file isn't a valid JSON Schema (error)

An input's schema or an agent's with.schema file isn't JSON, isn't an object (or boolean), or doesn't compile as a JSON Schema. $schema is ignored.

Fix: Fix the file: valid JSON, using draft-07 or 2020-12 keywords.

P055#

Bad agent.verify (error)

agent.verify must be last N with N from 1 to 1000.

Fix: E.g. verify: last 20.

P056#

Two pipelines poll one telegram bot (warning)

In one multi-file pipo check, two pipelines have telegram inputs for the same bot (or token). Telegram lets one reader take a bot's messages, so the second one refuses to start while the first runs.

Fix: Give each pipeline its own bot (with.bot), or receive with one bot and send with another.

P057#

Telegram send without chat_id (error)

A tap: telegram or to: telegram has no with.chat_id, and an input that isn't via: telegram reaches it. Only packets from a telegram input carry a chat to reply to.

Fix: Set with.chat_id.

P058#

exec command holds spaces (warning)

An exec step's or a command input's command looks like a whole command line (ffmpeg -i in.mp3). Neither runs a shell, so it would look for a program with that whole name.

Fix: Put only the program in command and each argument in args.

P059#

fn module can't run in the runner (error)

The fn module imports node:*/bun:* or a Node built-in, uses a host global (Bun, process, require, Buffer, fetch, …) other than in typeof, fails to bundle, or throws while loading. Functions run in the runner's embedded QuickJS. Reported by the CLI's pipo check and pipo compile, once the file has no other errors.

Fix: Keep the module to plain functions over the packet data; do anything that needs the system in a connector or an exec step.

P060#

Not exactly one of input and inputs, or too many inputs (error)

The pipeline has neither input: nor inputs:, has both, or has more than 16 inputs.

Fix: Keep input: for one input or inputs: for several. Feed a pipeline with many sources from other pipelines through one via: pipeline input.

P061#

Input name clashes (error)

An input is called output, or has the name of a node. Inputs and nodes share one set of names, since from: refers to both.

Fix: Rename the input or the node.

P062#

Two inputs clash (error)

Two http inputs of one pipeline take the same method and path, or set different listen ports (they share one listener), or two telegram inputs poll the same bot.

Fix: Give each http input its own path or method and at most one port; use one telegram input per bot and route on the data.

P063#

Bad chain link (error)

A pipeline feeds itself (to: pipeline naming itself, or listing itself in a via: pipeline input's from), or one sender is listed in two inputs.

Fix: Loop inside a pipeline with loop: instead; list each sender in one input only.

P064#

Chain declared at one end only, or a chain cycle (warning)

In one multi-file pipo check: a pipeline sends to: pipeline a receiver whose via: pipeline input doesn't list it (the receiver would refuse its packets), a receiver lists a sender that doesn't send to it, or chains form a cycle. Pipelines not in the same check aren't looked at.

Fix: Declare the link at both ends; break a cycle, or make sure a filter ends it (a packet is refused after 16 hand-offs).

P065#

select without parse: html (error)

A poll input sets select (a CSS selector) but reads its answer as json, text or feed, where there is no HTML to select from.

Fix: Set parse: html (or leave parse out, so an HTML answer is read as a page), or remove select.

P066#

Not an http or https URL (error)

A poll input's url, or a crawl input's start or within, isn't a whole http or https URL. Values with a ${…} template are checked when they are rendered instead.

Fix: Write the whole URL, scheme and host included, e.g. https://example.com/feed.xml.

Generated from the Pipo source code.Pipo is licensed under the AGPLv3.