headroomlabs-ai/headroom · error · ValueError
Unsupported spreadsheet format '{suffix}'. Supported: .xlsx,
Error message
Unsupported spreadsheet format '{suffix}'. Supported: .xlsx, .xls What it means
The spreadsheet ingest function dispatches on the lowercased file suffix and only accepts `.xlsx` and `.xls`; any other suffix raises ValueError listing the supported formats. Note the check is suffix-based, not content-based — a real Excel file with an unusual extension, or an unsupported spreadsheet format entirely, both land here.
Source
Thrown at headroom/transforms/spreadsheet_ingest.py:96
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
- Convert the file to `.xlsx` before ingest (LibreOffice headless, pandas round-trip) for supported-but-unlisted formats like `.xlsm`/`.ods`
- Route by content type upstream: send `.csv` to a CSV reader and only pass `.xlsx`/`.xls` to this API
- Pre-validate the suffix in your intake layer and reject with a user-facing message listing accepted formats
Example fix
# before
sheets = ingest_spreadsheet(Path("export.ods"))
# after
from pathlib import Path
SUPPORTED = {".xlsx", ".xls"}
path = Path("export.ods")
if path.suffix.lower() not in SUPPORTED:
subprocess.run(["libreoffice", "--headless", "--convert-to", "xlsx", path], check=True)
path = path.with_suffix(".xlsx")
sheets = ingest_spreadsheet(path) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
SUPPORTED_SUFFIXES = {".xlsx", ".xls"}
def spreadsheet_or_none(path: str | Path) -> Path | None:
p = Path(path)
return p if p.suffix.lower() in SUPPORTED_SUFFIXES else None
if (p := spreadsheet_or_none(upload)) is None:
return reject_upload(upload, accepted=list(SUPPORTED_SUFFIXES))
sheets = ingest_spreadsheet(p) Type guard
def is_supported_spreadsheet(path: str | Path) -> bool:
return Path(path).suffix.lower() in {".xlsx", ".xls"} Try / catch
try:
sheets = ingest_spreadsheet(path)
except ValueError as e:
if "Unsupported spreadsheet format" in str(e):
sheets = ingest_csv(path) # route to the right reader
else:
raise Prevention
- Validate suffix at the intake/API boundary with a clear user-facing error
- Route .csv and other formats to their own readers before calling spreadsheet ingest
- Convert .xlsm/.ods uploads to .xlsx at upload time
When it happens
Trigger: Passing a `.csv`, `.xlsm`, `.ods`, `.numbers` file, or a path with no/odd extension (`.XLSX~`, temp-file suffixes) — anything whose `Path.suffix.lower()` is not exactly `.xlsx` or `.xls`.
Common situations: Users uploading LibreOffice/Google-Sheets exports in `.ods` or `.xlsx` variants like `.xlsm` (macro workbooks, which openpyxl read-only mode doesn't cover); temp files mid-write with mangled suffixes; routing logic that forwards any 'attachment' to the spreadsheet ingest.
Related errors
- Spreadsheet not found: {p}
- 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/5eca25803297fd94.
Report an issue: GitHub.