1
5 Comments

Duplicate CSV keys used to pick a row. I made that fail instead.

I kept matching two export CSVs in a spreadsheet: Stripe vs books, Shopify vs warehouse, bank export vs ledger. Row-diff was the wrong job. I needed a key.

Local HTML utility: two UTF-8 CSVs, exact ID or ordered composite key, report of matched / only-A / only-B / changed fields. Column names can differ. Files stay on this machine. No app account, no telemetry in the utility, no bank connection.

If a key is duplicated, matching stops and cites the records. It does not silently pick a row. In-app example: id=001 blocked.

Limits: exact case-sensitive strings; 10,000 combined data records; 2 MB per file; 200 columns; v0.3.0. Edge renders the in-app examples. I have not seen a stranger pay.

Also on the same store, not this thread: CSV Doctor checks one file’s encoding/delimiter/headers before you recon two files.

https://payhip.com/b/13wWp — $39. Not asking you to buy it. If you recon two exports, what key do you actually use, and should duplicate keys hard-fail or warn?

on September 8, 2026
  1. 1

    The duplicate key refusal is the whole product, and I suspect it is also your marketing problem.

    We had the same shape from the other side. Our launch checklist had one h1 per page on it, and two pages shipped with no h1 at all for months. A checklist cannot fail, so it never caught anything. Twenty lines of curl counting h1 tags failed on the first run. You have built that and put a price on it.

    But the value of a refusal is invisible. When it works, nothing happens, and nobody feels rescued from a thing that never occurred to them. That is a hard sell at any price, and it has nothing to do with the 39.

    Which is why I would not demo the matching. Demo the counterfactual: here is a file with a duplicated key, here is the row a naive tool silently picks, here is what that does to the reconciliation downstream, here is us stopping and citing it. Make the failure visible, because the failure is the product.

    1. 1

      Yes. The refusal is the product, and it is also why a screenshot of “3 matched, 1 changed” sells nothing.

      The in-app duplicate example already stops and cites id=001 / records 2 and 3. What it does not show is the row a lookup would have kept and what that does to a total. That counterfactual is not in v0.3.0. I will put it next to the block: naive pick vs halt.

      A checklist that cannot fail is the right analogy. Thank you for writing it plainly.

  2. 1

    Hard-failing on duplicate keys instead of silently picking a row is the production pattern most vibe-coded recon tools skip.

    Keep that fail-closed instinct for every path: loading, empty, error, and "matched but fields changed." Sketch can invent happy paths. Durable product owns the ugly ones from day one.

    Free Pyramid Reality Check (core job + states before more polish):
    https://durablefoundations.gumroad.com/l/pyramid-reality-check

    For the people who actually recon Stripe vs books, is hard-fail still the right default, or do they want warn-and-continue with a cited list?

    Kael Voss / DurableFoundations

  3. 1

    How I key these, and the fail mode I want.

    Stripe vs books: Stripe object ID (ch_ / pi_) in a memo — never amount (gross vs net). Bank vs Stripe: payout ID (po_) when I have it.

    Shopify vs warehouse: order id; line level is (order, SKU). Two identical lines on one order can be legitimate.

    Bank vs ledger: often no honest exact key. Date+amount+description becomes only-A / only-B noise.

    Hard-fail with a citation. Warn-and-continue is a silent pick with a log line.

    Not in this version: near-miss counts after trim/# / leading zeros (report only); group-key sums without choosing a row.

  4. 1

    OS: unzip, open CSVCompare.html in a desktop browser. Built-in buttons: id=001 qty 2→5 (004 added, 003 removed); sku+warehouse → product_code+location; duplicate id=001 blocked (before records 2 and 3). Limits: 10k rows, 2MB/file, exact strings.