Skip to main content
Every pikopod command exits with one of three codes, and the separation between 1 and 2 is the point. A build that fails because your provider shipped a breaking change needs a different response from a build that fails because a token expired. A CI gate that collapses the two teaches people to ignore both. 2 also covers results pikopod cannot stand behind. pikopod fix exits 2 when its impact scan finds nothing, because a literal scan cannot distinguish “this field is unused” from “this codebase spells it differently”. See fix.

Readers versus gates

pikopod incidents is a reader, not a gate. It exits 0 whether or not it found anything, and 2 only when it cannot read the event log. Nothing about “I found incidents” is an exit-code signal. Script against --format json instead, where total_matching and truncated are always present so a shortened list can never be mistaken for a clean one.

Reproduce has no exit 1

pikopod scenario reproduce exits 0 when it writes a pack and 2 when it refuses: when the incident’s recording has aged out of retention, or when the pack ran and the failure did not recur, which means pikopod cannot stand behind it as a reproduction. It never fabricates a request to avoid refusing. The contrast with its sibling is deliberate. pikopod scenario from-drift exits 1 on a failed replay because its pack pins the old contract, so failing confirms the drift. reproduce is inverted: its pack recreates a failure, so failing means the recreation did not work. One is a finding; the other is an unverifiable result.

Where UNVERIFIABLE lands

UNVERIFIABLE is a real outcome, distinct from a pass, but it is not a fourth exit code. It maps onto the existing ones by what it means:
  • conformance counts checks whose evidence the sanitizer removed and reports them as neither pass nor violation, leaving the exit code alone.
  • fix and scenario reproduce exit 2, because a result pikopod cannot stand behind is a tool outcome, never a finding.
  • Over MCP, every tool returns the verdict explicitly. See MCP tools.

Schema versions

schema/drift-event.schema.json is the other stable surface. Its schema_version field is the protocol number, pinned in the schema and in the producer at the same time, and a test fails the build if they diverge. The rule for what moves the version:
  • New kind values do not bump it. Treat a kind you do not recognise as informational.
  • New fields do not bump it. Ignore the ones you do not know.
  • Structural changes do: a field removed, a type changed, or an existing field’s meaning changed.
If you validate strictly against v1’s enum, incident events will fail until you widen. If you check schema_version == "1" for equality, switch to a minimum check. Incidents carry the concrete status in after ("503") and, like every event, a path template in endpoint, never a concrete path. See Drift event schema.