JuliusBrussee/caveman · error · CatalogError
: invalid YAML
Error message
{path}: invalid YAML: {error} What it means
CatalogError raised by load_yaml() when a provider catalog file cannot be parsed as YAML: the file is unreadable (OSError) or yaml.safe_load raises YAMLError. The message includes the path and the underlying parser error, and chains the original exception.
Solutions
- Run the file through a YAML linter (yamllint) or python -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" <file> to see the exact parser error
- Fix the syntax at the line/column reported in the error message (commonly tabs instead of spaces or an unquoted special character)
- Verify the path is correct and readable (ls -l / test -r) — an OSError produces the same message
- If the file was machine-generated, regenerate it with the producing tool rather than patching by hand
Example fix
# before (providers.yaml) providers: - name: acme # tab -> YAMLError // after providers: - name: acme # spaces only
Defensive patterns
Strategy: validation
Validate before calling
import yaml
from pathlib import Path
def yaml_parsable(path: str) -> bool:
try:
yaml.safe_load(Path(path).read_text(encoding="utf-8"))
return True
except (OSError, yaml.YAMLError):
return False
# run yamllint or this check before validate_catalog Type guard
def is_readable_file(p) -> bool:
from pathlib import Path
return Path(p).is_file() and os.access(p, os.R_OK) Try / catch
try:
rows = load_yaml(Path(path))
except CatalogError as e:
log.error("fix YAML syntax or path: %s", e)
raise SystemExit(2) Prevention
- Run yamllint on catalog files in CI before validation
- Use spaces, never tabs, in YAML files
- Quote strings containing special characters (: - # {) in catalog entries
- Verify file paths in scripts/CI configs after moving or renaming catalog files
When it happens
Trigger: Running validate_catalog (which calls load_yaml) against a catalog path that: doesn't exist or lacks read permission (OSError branch), or contains invalid YAML — bad indentation, tabs, unescaped special chars, duplicate anchor misuse, or invalid UTF-8 handling errors surfacing as YAMLError.
Common situations: Hand-edited catalog with a stray tab or unclosed quote; wrong path passed on the CLI (typo or file moved); file created by a tool emitting JSON-ish YAML that isn't valid; CI checkout with line-ending or encoding issues.
Related errors
- : catalog must be a non-empty list
- Aider config has duplicate openai-api-base keys; refusing…
- Aider config has duplicate read keys; refusing unsafe merge
- Aider config uses inline/scalar read; use block-list form…
- auth_token: in is ignored — the inbound token is read only…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/9b8dd218be452749.
Report an issue: GitHub.
Appendix: source
Thrown at shared/provider-catalog/validate_catalog.py:93
# comment line starting with this literal. verified_at is price provenance — the
# catalog_version a signed receipt attests — so a proposal that merges with its
# marker intact attests a price check nobody performed. Deleting the marker is
# the reviewer's statement that they confirmed the number against the provider's
# own pricing page; until then, the catalog is invalid.
# MUST stay a prefix of REVIEW_MARKER_PREFIX in that script (the test suite
# asserts it), or a renamed marker would sail past this rule.
REVIEW_MARKER = "# proposed-by:"
class CatalogError(ValueError):
pass
def load_yaml(path: Path) -> list[dict[str, Any]]:
try:
document = yaml.safe_load(path.read_text(encoding="utf-8"))
except (OSError, yaml.YAMLError) as error:
raise CatalogError(f"{path}: invalid YAML: {error}") from error
if not isinstance(document, list) or not document:
raise CatalogError(f"{path}: catalog must be a non-empty list")
if not all(isinstance(row, dict) for row in document):
raise CatalogError(f"{path}: every catalog row must be an object")
return document
def parsed_time(value: Any, label: str) -> datetime:
if isinstance(value, datetime):
parsed = value
elif isinstance(value, str):
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as error:
raise CatalogError(f"{label}: verified_at must be RFC3339") from error
else:
raise CatalogError(f"{label}: verified_at must be an RFC3339 timestamp")
if parsed.tzinfo is None:View on GitHub (pinned to 3ee70a1026)