[object Object]

← back to Dw Title Repair

TK-11376: re-read 3x before declaring a product not_found

2933b94faed376044e1a0a28434d1f5f25c95ee5 · 2026-09-10 09:45:51 -0700 · steve

Observed mid-run: the verify read returned null for two products that
demonstrably exist and are ACTIVE (they had already been renamed in stage 1).
A transient null was being treated as 'deleted' and skipped. Harmless for an
already-applied product, but an unapplied one could have been silently missed.
Now re-reads 3x with backoff before believing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Files touched

Diff

commit 2933b94faed376044e1a0a28434d1f5f25c95ee5
Author: steve <steve@designerwallcoverings.com>
Date:   Thu Sep 10 09:45:51 2026 -0700

    TK-11376: re-read 3x before declaring a product not_found
    
    Observed mid-run: the verify read returned null for two products that
    demonstrably exist and are ACTIVE (they had already been renamed in stage 1).
    A transient null was being treated as 'deleted' and skipped. Harmless for an
    already-applied product, but an unapplied one could have been silently missed.
    Now re-reads 3x with backoff before believing it.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
 apply.mjs | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/apply.mjs b/apply.mjs
index 0fa96f2..efd27dc 100755
--- a/apply.mjs
+++ b/apply.mjs
@@ -25,9 +25,17 @@ const Q=`mutation($id:ID!,$t:String!){productUpdate(input:{id:$id,title:$t}){pro
 let ok=0,skip=0,err=0;
 for(const [i,r] of work.entries()){
   // VERIFY-BEFORE-WRITE: only write if the live title is still what we recorded
-  const cur=await gql(`{product(id:"${r.id}"){title}}`);
-  const live=cur?.data?.product?.title;
-  if(live===undefined){err++;ledger.write(JSON.stringify({id:r.id,status:'not_found'})+'\n');continue;}
+  // A null/undefined product can be a TRANSIENT read failure, not a deleted product —
+  // observed mid-run on products that demonstrably exist and are ACTIVE. Re-read before
+  // believing it, so a blip can never silently skip an unapplied product.
+  let live;
+  for(let a=0;a<3;a++){
+    const cur=await gql(`{product(id:"${r.id}"){title}}`);
+    live=cur?.data?.product?.title;
+    if(live!==undefined) break;
+    await new Promise(s=>setTimeout(s,800*(a+1)));
+  }
+  if(live===undefined){err++;ledger.write(JSON.stringify({id:r.id,status:'not_found_after_3_reads'})+'\n');continue;}
   if(live===r.to){skip++;ledger.write(JSON.stringify({id:r.id,status:'already_target'})+'\n');continue;}
   if(live!==r.from){skip++;ledger.write(JSON.stringify({id:r.id,status:'drifted',live,expected:r.from})+'\n');continue;}
   if(!APPLY){ok++;ledger.write(JSON.stringify({id:r.id,status:'DRYRUN',from:r.from,to:r.to})+'\n');continue;}

← 48ff5d4 TK-11376 supplement: recover 920 more products via dw_sku me  ·  back to Dw Title Repair  ·  TK-11376: add supplement rollback (t8) and document the 17-i 36da240 →