headroomlabs-ai/headroom · error · FileNotFoundError
Spreadsheet not found: {p}
Error message
Spreadsheet not found: {p} What it means
The spreadsheet ingest entry point checks `Path(path).exists()` before dispatching on suffix and raises FileNotFoundError with the exact offending path when the file is missing. This is a straightforward precondition failure: the caller referenced a spreadsheet that does not exist at that location (or is not readable at that path, e.g. inside a container).
Source
Thrown at headroom/transforms/spreadsheet_ingest.py:89
def load_spreadsheet(path: str | Path) -> dict[str, str]:
"""Load a spreadsheet file into ``{sheet_name: csv_text}``.
Args:
path: Path to a ``.xlsx`` or ``.xls`` file.
Returns:
Mapping of sheet name to CSV-rendered text (empty sheets omitted).
Raises:
FileNotFoundError: If the path does not exist.
ValueError: If the file extension is unsupported.
ImportError: If the required parser dependency is not installed.
"""
p = Path(path)
if not p.exists():
raise FileNotFoundError(f"Spreadsheet not found: {p}")
suffix = p.suffix.lower()
if suffix == ".xlsx":
return _load_xlsx(p)
if suffix == ".xls":
return _load_xls(p) # pragma: no cover - legacy .xls path, see _load_xls
raise ValueError(f"Unsupported spreadsheet format '{suffix}'. Supported: .xlsx, .xls")
View on GitHub (pinned to 322425c43b)
Solutions
- Check the path exists before calling (see validation code) and log/resolve it early with `Path.resolve()`
- Use absolute paths built from a configured base directory instead of cwd-relative strings
- If the file may legitimately disappear (async/queued ingestion), treat FileNotFoundError as a retryable condition in the caller
Example fix
# before
path = Path("uploads/q3.xlsx")
sheets = ingest_spreadsheet(path)
# after
path = (UPLOAD_DIR / "q3.xlsx").resolve()
if not path.exists():
raise FileNotFoundError(f"upload vanished: {path}")
sheets = ingest_spreadsheet(path) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def existing_spreadsheet(path: str | Path) -> Path:
p = Path(path).resolve()
if not p.is_file():
raise FileNotFoundError(f"no spreadsheet at {p}")
return p
sheets = ingest_spreadsheet(existing_spreadsheet(user_path)) Type guard
def is_existing_file(path: str | Path) -> bool:
return Path(path).is_file() Try / catch
try:
sheets = ingest_spreadsheet(path)
except FileNotFoundError:
log.warning("spreadsheet vanished before ingest: %s", path)
mark_job_retryable(job_id) # queued-upload race
Prevention
- Resolve paths against an explicit base directory; never rely on cwd
- Use absolute paths in configs and job queues
- For async pipelines, treat missing files as a retryable state with a bounded attempt count
When it happens
Trigger: Calling `ingest_spreadsheet(Path("data/report.xlsx"))` when the file was never created, was moved/renamed, or when the process's working directory differs from where the caller assumes the relative path resolves.
Common situations: Relative paths in scripts run from a different cwd (cron, CI, Docker WORKDIR); user-uploaded file already cleaned up by the time a background worker processes it; typos or stale paths in config; file present locally but absent in the container image.
Related errors
- Unsupported spreadsheet format '{suffix}'. Supported: .xlsx,
- target_ratio list length {len(target_ratio)} does not match
- ccr_originals list length {len(ccr_originals)} does not matc
- token_url is required
- invalid pipeline config TOML: {0}
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/03e1b4cb9014c6f2.
Report an issue: GitHub.