A spec that does not parse escapes as a raw yaml.scanner.ScannerError, rather than the clean refusal a bad spec is supposed to get. This contradicts a recorded invariant — "A read-only verb never tracebacks" — and the two verbs it names are exactly the two affected.
Reproduce
lc init . && printf 'version: "0.1"\nname: broken\noutputs:\n - id: fit\n recipe:\n command: echo a: b > {output}\n' > astra.yaml
lc status
| verb |
behaviour |
lc status |
raw ScannerError traceback |
lc materialize --check |
raw ScannerError traceback |
lc materialize |
usually masked — the dirty-tree refusal fires first |
The masking on materialize is worse than helpful: it makes the bug look narrower than it is.
Cause
materialize.status → materialize._classified → materialize._graph
→ plan.build → plan._validate
→ astra.validation.validate_analysis_schema
→ astra.helpers.load_yaml ← raises
plan._validate is the gate that turns a bad spec into ASTRA's own errors, but it only catches what validation reports. A spec that does not parse raises inside the loader before there is anything to validate.
Shape of a fix
A yaml.YAMLError catch in plan._validate, naming the file and the line the way the validator's own errors are rendered. YAML errors carry problem_mark (file, line, column), so the message can be as specific as astra's.
Notes
Pre-existing; found while writing a nested test project during #199, and recorded in CLAUDE.md under the invariant it contradicts.
A spec that does not parse escapes as a raw
yaml.scanner.ScannerError, rather than the clean refusal a bad spec is supposed to get. This contradicts a recorded invariant — "A read-only verb never tracebacks" — and the two verbs it names are exactly the two affected.Reproduce
lc statusScannerErrortracebacklc materialize --checkScannerErrortracebacklc materializeThe masking on
materializeis worse than helpful: it makes the bug look narrower than it is.Cause
plan._validateis the gate that turns a bad spec into ASTRA's own errors, but it only catches what validation reports. A spec that does not parse raises inside the loader before there is anything to validate.Shape of a fix
A
yaml.YAMLErrorcatch inplan._validate, naming the file and the line the way the validator's own errors are rendered. YAML errors carryproblem_mark(file, line, column), so the message can be as specific as astra's.Notes
Pre-existing; found while writing a nested test project during #199, and recorded in
CLAUDE.mdunder the invariant it contradicts.