Skip to content

T&E re-upload that fails every row still overwrites the earlier load's registry record #215

Closed

Description

@amar-python

What happens

Re-uploading a file in T&E mode where every row fails still replaces the registry record of the earlier successful load. The rows from that earlier load stay in the table, but the registry (what "Migrated files" shows) now says nothing was loaded, and the API returns status: ok.

This is the gap left by #170. Its guard covers first-time uploads only.

Reproduction (clean database, main at ad39ddf, real API + PostgreSQL 16)

Upload the same 10-row organisations CSV twice, mode: "te", targetTable: "organisations" (6 rows valid, 4 invalid on the first run: duplicate name, bad org_type, country too long, null name):

API response csv_files registry rows in tenant_dev.organisations audit_log rows
after upload 1 ok, inserted 6, failed 4 row_count 6, failed_rows 4 6 1
after upload 2 ok, inserted 0, failed 10 row_count 0, failed_rows 10 6 2

After upload 2 the registry contradicts the table: it says 0 rows were loaded from a file whose rows are sitting in the table. The only surviving record of the 6-row load is the audit log.

Root cause

api/services/te_loader.py:

The comment at :217 says this ordering is deliberate ("does not undo the registry cleanup … reupload compat"). That trade-off is what loses the record.

Why it matters

The registry is the ledger a verification or reconciliation step reads: "file X, N rows loaded, M failed". After this sequence it misreports a successful migration as a total failure. For T&E mode the earlier rows cannot be dropped with the registry entry (the table is shared), so the two diverge permanently.

Proposed

Defer _cleanup_prior_registry_entries() until inserted > 0; apply the all-failed rollback to re-uploads as well, returning the same clean status: error response and leaving the prior registry row alone. Keep the permission check (_require_delete) where it is.

Done when

Related

#170 (closed, first-upload case), #175 (closed, overwrite permission), #109 (M5: per-tenant dedup will change re-upload semantics, so fix the guard now rather than wait).

Found while producing the sample Migration Verification Report for #59.


Generated by Claude Code

Activity

  1. amar-python commented on Nov 9, 2026

    @amar-python
    OwnerAuthor

    Fix in #220 (draft). Approved approach: cleanup moves to after at least one row has landed, and the all-failed rollback now covers re-uploads, so an all-failed re-upload returns status: error and leaves the registry alone. Full suite: 391 tests, 388 passed, 0 failed, 3 environment errors that fail identically on main.


    Claude Code


    Generated by Claude Code

  2. added 2 commits that reference this issue on Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions