Skip to content

data ingest: image preflight green-lights a labels.csv with no "filename" column (image_id) → cluster fails at transfer (exit 9) #371

Description

@LukasWodka

Part of epic tracebloc/backend#1142 (Theme D — preflight parity). Sibling of #352.

MED. tb data ingest for image tasks green-lights a dataset the cluster will reject for a structural reason the local walk can already see — an accept-locally-then-fail-after-upload parity gap.

Symptom

A labels.csv whose file column is named image_id (not filename):

  • tb data ingest <dir> --task image_classification --label-column label --target-size 32x32 --dry-run reports ✔ Dry-run complete — your data and workspace check out.
  • The real ingest then fails cluster-side with exit 9: every record logs ERROR … file_transfer.py:182 | No filename found in record, 20/20 file-transfer failures, 0 records ingested.

Root cause

Two asymmetric preflight paths for "which column names the file":

Text tasks Image tasks
resolver matchColumnIndex(header, "filename") imageFileColIndex(header)
column missing hard error ("Add a filename column and re-run") falls back to column 0 — reads image_id values as filenames, finds the files, passes

The ingestor's contract (layout.v1.json) declares requires_filename_column: true for every image task, and the cluster reads each image's file via a case-sensitive record.get("filename") at transfer time (data-ingestors file_transfer.py:179). It accepts exactly one key — literal filename and never aliases another column. So image_id (and data_id) resolve to no key → every row fails at transfer.

The ingestor's own preflight (validate_data) does not catch this — IngestableRecordsValidator passes when the column is absent ("not this validator's error to raise") — so it only surfaces at transfer time. That also blinded the parity harness: imgc-ok's golden is generated from validate_data (accept), the CLI also accepted (via the column-0 fallback), so accept/accept looked green.

Secondary latent bug: imageFileColIndex also accepted data_id as an alias with a comment claiming "a data_id CSV is ingested cleanly by the cluster" — it is not; a data_id-only manifest fails at transfer identically to image_id.

Even the CLI's own parity fixture was wrong: imgc-ok/labels.csv (and every other imgc-* fixture) used image_id,label, so the CLI's canonical happy-path example would not actually ingest. (Text fixtures correctly use filename,label, and text ingests fine.)

Fix (this PR)

  • Enforce the filename-column contract up front for the image family (CheckImageFilenameColumn), mirroring the text family's check — reject in the dry-run with an actionable message before any upload.
  • Drop the data_id false-accept and the column-0 fallback from imageFileColIndex (require literally filename, case-insensitive + trimmed like the ingestor's _match_column).
  • Correct all 17 imgc-* parity fixtures to filename, add a new imgc-no-filename-col case that pins the divergence (ingestor preflight accepts, CLI rejects the doomed upload), regenerate goldens against the pin, and fix the unit-test CSVs + help text.

Follow-up (separate issue)

The transfer read is case-sensitive (record.get("filename")) while the ingestor's preflight validator matches case-insensitively, so a header like Filename passes preflight yet fails transfer — the same #340-class asymmetry already fixed for the label column, still open for filename. Best fixed ingestor-side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions