{"record":{"id":"9b8dd218be452749","repo":"JuliusBrussee/caveman","slug":"path-invalid-yaml-error","errorCode":null,"errorMessage":"{path}: invalid YAML: {error}","messagePattern":"(.+?): invalid YAML: (.+?)","errorType":"validation","errorClass":"CatalogError","httpStatus":null,"severity":"error","filePath":"shared/provider-catalog/validate_catalog.py","lineNumber":93,"sourceCode":"# comment line starting with this literal. verified_at is price provenance — the\n# catalog_version a signed receipt attests — so a proposal that merges with its\n# marker intact attests a price check nobody performed. Deleting the marker is\n# the reviewer's statement that they confirmed the number against the provider's\n# own pricing page; until then, the catalog is invalid.\n# MUST stay a prefix of REVIEW_MARKER_PREFIX in that script (the test suite\n# asserts it), or a renamed marker would sail past this rule.\nREVIEW_MARKER = \"# proposed-by:\"\n\n\nclass CatalogError(ValueError):\n    pass\n\n\ndef load_yaml(path: Path) -> list[dict[str, Any]]:\n    try:\n        document = yaml.safe_load(path.read_text(encoding=\"utf-8\"))\n    except (OSError, yaml.YAMLError) as error:\n        raise CatalogError(f\"{path}: invalid YAML: {error}\") from error\n    if not isinstance(document, list) or not document:\n        raise CatalogError(f\"{path}: catalog must be a non-empty list\")\n    if not all(isinstance(row, dict) for row in document):\n        raise CatalogError(f\"{path}: every catalog row must be an object\")\n    return document\n\n\ndef parsed_time(value: Any, label: str) -> datetime:\n    if isinstance(value, datetime):\n        parsed = value\n    elif isinstance(value, str):\n        try:\n            parsed = datetime.fromisoformat(value.replace(\"Z\", \"+00:00\"))\n        except ValueError as error:\n            raise CatalogError(f\"{label}: verified_at must be RFC3339\") from error\n    else:\n        raise CatalogError(f\"{label}: verified_at must be an RFC3339 timestamp\")\n    if parsed.tzinfo is None:","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/provider-catalog/validate_catalog.py#L75-L111","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (providers.yaml)\nproviders:\n\t- name: acme   # tab -> YAMLError\n// after\nproviders:\n  - name: acme   # spaces only","handlingStrategy":"validation","validationCode":"import yaml\nfrom pathlib import Path\ndef yaml_parsable(path: str) -> bool:\n    try:\n        yaml.safe_load(Path(path).read_text(encoding=\"utf-8\"))\n        return True\n    except (OSError, yaml.YAMLError):\n        return False\n# run yamllint or this check before validate_catalog","typeGuard":"def is_readable_file(p) -> bool:\n    from pathlib import Path\n    return Path(p).is_file() and os.access(p, os.R_OK)","tryCatchPattern":"try:\n    rows = load_yaml(Path(path))\nexcept CatalogError as e:\n    log.error(\"fix YAML syntax or path: %s\", e)\n    raise SystemExit(2)","preventionTips":["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"],"tags":["yaml","config","parsing","catalog"],"backgroundTag":"yaml-parse-error","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}