Skip to main content
Every canonic error carries a stable, wire-safe code string plus a headless process exit code; never free text alone, so a caller (script, CI job, agent) can act on it programmatically. The registry lives in canonic/exc.py and is guarded by conformance tests, so this mapping can’t silently drift.
Exit codecodeMeaning
2unresolvedA metric name matches no active canonical binding.
3ambiguousA name matches more than one active binding; candidates are returned.
4unreachableA dimension/filter has no declared join path to the metric’s source.
5ambiguous_join_pathMore than one valid join path exists; the query must name an explicit via.
6unsupported_measureA non-additive/semi-additive measure was requested at a grain no strategy can safely serve.
7fanout_unsafeA join would corrupt a non-additive measure.
8guardrail_blockA severity: error guardrail blocked the query.
9validation_failedA semantic/contract/knowledge file failed validation.
10assertion_failedAn assertion diverged from its expected value beyond tolerance (also used by canonic assert’s --min-accuracy gate).
11read_only_violationA non-SELECT statement was submitted to a read-only execution path.
12schema_mismatchDeclared schema doesn’t match the live source during probe validation.
13connection_errorA connector couldn’t establish or maintain a connection (also raised for an unregistered connector type, or an out-of-range connector version).
14contradictionHeadless --strict ingest found a run with any flagged contradiction.
15generation_failedAn LLM generation call failed (deterministic provider/transport error).
16structured_output_invalidThe model’s output doesn’t satisfy the requested JSON schema.
17structured_output_unsupportedThe model/endpoint can’t honor schema-constrained output at all.
18air_gapped_violationAir-gapped mode would let context leave the machine (public endpoint, remote secret ref, or telemetry).
19retries_exhaustedA transient provider/transport failure persisted past the bounded retry budget.

Resolving ambiguous

When a name matches more than one active binding, the error carries a candidates list; the exact, unambiguous values to re-issue the request with. Both --json and plain-text CLI output, and the MCP tool error payload, include it. Ambiguous dimension: a dimension name is declared on more than one join-reachable source (e.g. country exists on both customers and a twice-joined locations, reached as pickup/dropoff):
canonic query --metrics rental_revenue --dimensions country
error ambiguous: dimension 'country' is present on multiple join-reachable sources; qualify explicitly
  candidate 1: customers.country
  candidate 2: dropoff.country
  candidate 3: locations.country
  candidate 4: pickup.country
  hint: qualify with one of the candidates above, e.g. --dimensions customers.country
Qualify with alias.dimension: the alias is the join’s name (or the source name for an unnamed join); to pick the one you mean:
canonic query --metrics rental_revenue --dimensions customers.country
Ambiguous metric name: two active bindings share a name/alias. The candidates list names each competing metric; re-issue with the specific canonical name (or fix the duplicate alias in contracts/metrics/) rather than the ambiguous shared one.

Errors without a registry code

A few internal/caller-contract errors carry no wire ErrorCode and use the default exit code 1: CapabilityNotSupportedError (a connector was asked to honor a capability it doesn’t declare), EmbeddingUnavailable (embed called without gating on is_available()), CredentialError, SemanticSourceError, ContractError, and KnowledgePageError. These signal a caller/config mistake rather than a documented, structured failure mode.
EvalDatasetError (a malformed canonic eval baseline dataset/candidates file) and KnowledgeReferenceError (a broken sl_ref/page link) both reuse validation_failed (exit 9) rather than defining new codes; they’re the same class of failure as any other invalid input file.