Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Diagnostic codes — the greppable index

Every proef diagnostic carries a stable code (proef::<area>::<name>), printed above the rendered message. Codes are a contract: they never change meaning, and searching this file (or tests/errors/) for a code you hit is the fastest route to the cause. The corpus column marks codes with a seeded broken example under tests/errors/<area>__<name>/ — dry-running one shows the exact rendered output.

Severity is error (fails validation/exit 2) unless marked warning.

proef::feature::* — feature-file parsing and expansion

CodeMeaningCorpus
feature::parseThe file is not valid Gherkin (parser error, located)✓
feature::empty_fileThe file is empty — a Feature: header is required
feature::no_examplesA Scenario Outline has no Examples rows
feature::ragged_examplesAn Examples row’s cell count differs from the header
feature::bad_examples_headerDuplicate or empty Examples column name
feature::unknown_placeholderAn outline step uses <name> not present in the header✓
feature::empty_scenarioA scenario has no steps (background included)✓

proef::pack::* — macro-pack loading and validation

CodeMeaningCorpus
pack::yamlThe pack file is not valid YAML✓
pack::duplicate_macroTwo macros share a name✓
pack::empty_macroA macro has neither steps: nor expect:
pack::steps_and_expectA macro has both steps: and expect:
pack::empty_expectAn expect: item asserts nothing✓
pack::empty_stepA step has no payload and no use:
pack::multiple_payloadsA step has more than one payload key
pack::unknown_step_kindThe payload key names no registered engine✓
pack::invalid_hurlThe payload does not parse as hurl (incl. zero-entry payloads)✓
pack::payload_invalidA structured payload fails the engine’s validator✓
pack::bad_referenceA ${…} reference in the payload cannot resolve at probe time
pack::retry_not_finiteretry/repeat is -1, 0, or above the 10000 cap✓
pack::delay_unboundeddelay exceeds the 1-hour cap
pack::option_declared_twiceretry/delay set both in the block’s [Options] and as the step’s own key, or a variable both supplied by a fragment’s [Options] variable: and given by a bind:✓
pack::pattern_bracesUnbalanced {/} in a match: pattern
pack::pattern_empty_capture{} with no capture name
pack::pattern_no_anchorA pattern with no literal word (capture-only)✓
pack::pattern_unknown_captureA {capture} that is not a declared param✓
pack::pattern_duplicate_captureThe same {capture} written twice in one pattern✓
pack::adjacent_captures{a} {b} with no literal between captures✓
pack::default_not_paramA defaults: key that is not a declared param✓
pack::bad_save_targetsaveAs: target other than global
pack::unknown_refref: names no loaded fragment (suggests the closest)✓
pack::duplicate_fragmentTwo fragment files declare the same # @proef name
pack::bad_annotationA fragment file the engine’s parser could not read, an annotation it could not attach, or a name containing # (which could never be referenced)
pack::unreadable_fragment_fileA fragment file that could not be read at all (encoding, permissions) — its siblings still load, and it is silent until something ref:s the corpus
pack::oversized_fragment_fileA fragment file over 8 MiB — skipped unread (the size comes from the directory entry), siblings still load
pack::fragment_corpus_too_largeThe corpus as a whole passed 64 MiB — the read stops, naming the file it stopped at
pack::body_form_conflictA step is both ref: and a payload (or use:)✓
pack::bind_without_refbind: with no ref: to read it — on a step (an inline block takes ${…} instead), or on a macro whose steps have none (a use: target resolves its own)✓
pack::unread_bind_keya bind: key no fragment in that scope reads (did-you-mean over the readable names) — the finer half of bind_without_ref, and the one a typo produces
pack::unknown_useuse: names no known macro✓
pack::use_cycleuse: composition forms a cycle✓
pack::use_too_deepuse: nesting exceeds depth 32
pack::use_with_modifiersA use: step carries modifiers that belong on the target
pack::use_with_payloadA step is both use: and a payload
pack::with_without_usewith: on a step that has no use:
pack::missing_use_paramThe use: target requires a param with: does not supply✓
pack::unknown_with_keyA with: key the target macro does not declare✓

proef::bind::* — matching prose to macros

CodeMeaningCorpus
bind::unbound_stepNo macro pattern matches the sentence (suggests the closest, plus a paste-ready macro stub)✓
bind::ambiguous_stepMore than one macro matches (all candidates listed)✓
bind::missing_paramA required param has no capture, table value, or default✓
bind::bad_tableA data table has an unusable shape
bind::unknown_table_keyA table key that is not a declared param
bind::table_conflictA table value collides with a pattern capture✓
bind::docstring_unusedA docstring the macro never references (warning)

proef::lower::* — lowering to engine batches

CodeMeaningCorpus
lower::then_before_whenAn expect: step with no previous request to attach to✓
lower::bad_statusexpect: status: is not an HTTP status number
lower::kind_unroutedInternal safety net: a lowered step’s kind maps to no engine (registry drift — unreachable through the CLI, which is why it has no corpus case)
lower::expansion_too_deepMacro expansion exceeded depth 32 at run time
lower::unbound_placeholderA {{variable}} nothing supplies — read by the fragment, or inside a bind: value (as the engine’s parser reads it: a hurl function like {{newUuid}} is not a variable) — with no bind: in scope, no earlier capture, no fragment-own [Options] variable:, no earlier-sorting sibling literal, and no secret of that name
lower::bind_shadows_captureA literal bind: re-assigns a name an earlier step captured, so the bound value silently wins from that entry on (warning)
lower::multiline_bindA bind: value that resolves to a line break or control character (tab excepted) — a hurl [Options] variable: is a single-line scalar; the inline hurl: | form is what splices a multi-line body
lower::secret_in_composite_bindA bind: value mixes ${secret:…} into a larger string — bind the secret alone and put the surrounding text in the fragment
lower::dry_run_unknownA runtime-only global under --dry-run (warning)

proef::resolve::* — ${…} variable resolution

CodeMeaningCorpus
resolve::unknown_variable${name} found in no scope (suggests the closest)✓
resolve::missing_env${env:NAME} unset and no :-default given
resolve::missing_config_var${url:key} / ${vars:key} defined in neither proef.toml nor the active [env.<name>] (suggests the closest)✓
resolve::missing_global${global:key} absent from the World (strict mode)
resolve::unknown_namespace${ns:…} with an unknown namespace
resolve::unknown_run_field${run:…} other than ${run:id}
resolve::fake_unknown${fake:kind} names no generator (suggests the closest)
resolve::empty_referenceAn empty ${}
resolve::depth_exceededResolution passed depth 8 — a reference cycle

proef::emit::* — artifact emission

CodeMeaningCorpus
emit::invalid_artifactThe emitted artifact does not parse with the engine’s parser

proef::run::* — preparing a scenario to execute

CodeMeaningCorpus
run::asset_unstageableA file,…; asset could not be staged into the scenario’s asset root: absent beside the source that names it, named by a path proef will not follow, or claimed by two sources at once

proef::config::* — proef.toml loading

CodeMeaningCorpus
config::tomlThe file is not valid TOML for the config schema (located — the caret sits on toml’s own error span)
config::unreadableThe file (or an explicit --config path) cannot be read

proef::source::* — source access (LSP whole-suite analysis)

CodeMeaningCorpus
source::unreadableA discovered feature or pack source could not be read (surfaced by analyze_suite; the CLI treats an unreadable file as a system fault instead)

proef::tags::* — reserved-tag recognition

CodeMeaningCorpus
tags::reserved_tag_typoA tag looks like a reserved one (@quarantine, @skip) but is not exactly it, so it has no effect — a warning with the spelling it likely meant

Coverage note

The fragment-file codes (pack::duplicate_fragment, pack::bad_annotation, pack::unreadable_fragment_file, pack::unread_bind_key, lower::unbound_placeholder, lower::multiline_bind, lower::secret_in_composite_bind, run::asset_unstageable) are covered in crates/proef-cli/tests/fragments.rs rather than tests/errors/: they need a [run] fragments root, and the seeded corpus is deliberately config-independent.

The config::* codes are covered in crates/proef-cli/tests/cli.rs for the same reason: a broken proef.toml cannot live in the config-independent seeded corpus.

30 of the 77 codes carry a seeded corpus case today; the corpus guard asserts a minimum, not parity. When you add a diagnostic, add its code here and prefer seeding a tests/errors/<area>__<name>/ case alongside it. tags::reserved_tag_typo is a warning (a near-miss is not an error), so it cannot live in tests/errors/ — that corpus fails dry-run by design — and is covered by a unit test instead.

Every row here is a code some code path actually emits. That was not free: this file used to carry a pack::load row for a defensive case that never had a code (a non-diagnostic core failure while loading packs renders as a bare error:), so a reader who grepped for it found nothing and had no way to tell the index was wrong. A row nothing emits is worse than a missing row.