[object Object]

← back to Model Arena

fix(TK-12121): count thumbnail by fresh PNG not Chrome exit code; wall-clock deadline in daily runner

69020dbae15eaf4e68a32df7c38586a5caab8769 · 2026-09-26 08:41:59 -0700 · Steve Abrams

Chrome updated 2026-09-22 writes the screenshot then exits 2 (Teardown watchdog), so
run.thumb was never set, auto-judge never fired, and the daily battle timed out (exit=2)
on 09-22/24/25/26. The runner's 340-iteration cap also shrank to ~16min after settle,
pre-empting the 35min judge patience.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAadfW5L9VDTcwyeVxhfB2

Files touched

Diff

commit 69020dbae15eaf4e68a32df7c38586a5caab8769
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Sep 26 08:41:59 2026 -0700

    fix(TK-12121): count thumbnail by fresh PNG not Chrome exit code; wall-clock deadline in daily runner
    
    Chrome updated 2026-09-22 writes the screenshot then exits 2 (Teardown watchdog), so
    run.thumb was never set, auto-judge never fired, and the daily battle timed out (exit=2)
    on 09-22/24/25/26. The runner's 340-iteration cap also shrank to ~16min after settle,
    pre-empting the 35min judge patience.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01BAadfW5L9VDTcwyeVxhfB2
---
 scripts/daily-challenge.js | 9 +++++++--
 server.js                  | 9 ++++++++-
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/scripts/daily-challenge.js b/scripts/daily-challenge.js
index 3c78664..5913f52 100644
--- a/scripts/daily-challenge.js
+++ b/scripts/daily-challenge.js
@@ -88,8 +88,13 @@ if (require.main === module) (async () => {
   // judgeTimeout: observed max judge duration was 32min (Aug-04, 3/4 models errored on Mac1 down)
   // → wait 35min post-settle before giving up on judging (contrarian fix: was 8min, too short)
   const JUDGE_PATIENCE_MS = 35 * 60 * 1000;
+  // TK-12121: bound the wait by WALL CLOCK, not iterations. The old 340-iteration cap shrank to
+  // ~16min once fast-poll (3s) kicked in after settle, so it gave up before JUDGE_PATIENCE (35min)
+  // could ever apply and misreported a settled-but-unjudged battle as "not settled".
+  const SETTLE_DEADLINE_MS = 45 * 60 * 1000;
+  const startedAt = Date.now();
   let settledAt = null;
-  for (let i = 0; i < 340; i++) {
+  while (settledAt ? true : (Date.now() - startedAt) < SETTLE_DEADLINE_MS) {
     await sleep(settledAt ? 3000 : 8000);
     const cur = await req('GET', '/api/challenges/' + c.id);
     const settled = Array.isArray(cur.runs) && cur.runs.length > 0 &&
@@ -110,7 +115,7 @@ if (require.main === module) (async () => {
       return;
     }
   }
-  console.error('timed out waiting for battle to settle'); process.exit(2);
+  console.error('timed out waiting for battle to settle (' + SETTLE_DEADLINE_MS/60000 + 'min)'); process.exit(2);
 })().catch(e => { console.error(e); process.exit(1); });
 
 module.exports = { POOL, TWISTS, recentIds, choosePick, buildBrief };
diff --git a/server.js b/server.js
index a8968ea..5bb5752 100644
--- a/server.js
+++ b/server.js
@@ -29,9 +29,16 @@ const CHROME_BIN = process.env.CHROME_BIN || '/Applications/Google Chrome.app/Co
 // render an artifact HTML file to a PNG thumbnail (best-effort, never throws)
 function shootThumb(htmlPath, outPng, cb) {
   if (!fs.existsSync(CHROME_BIN)) return cb && cb(new Error('no chrome'));
+  // TK-12121: Chrome (updated 2026-09-22) writes the PNG then exits 2 on "Teardown watchdog
+  // expired". Judge success by a FRESH non-empty PNG, not the exit code — the old err-first check
+  // left run.thumb unset, so auto-judge never fired and the daily runner timed out (exit=2).
+  try { fs.unlinkSync(outPng); } catch {} // a stale png must never count as a fresh shot
   execFile(CHROME_BIN, ['--headless', '--disable-gpu', '--no-sandbox', '--hide-scrollbars',
     '--window-size=800,600', '--virtual-time-budget=2800', '--screenshot=' + outPng,
-    'file://' + htmlPath], { timeout: 20000 }, (err) => cb && cb(err || (fs.existsSync(outPng) ? null : new Error('no png'))));
+    'file://' + htmlPath], { timeout: 20000 }, (err) => {
+      let ok = false; try { ok = fs.statSync(outPng).size > 0; } catch {}
+      cb && cb(ok ? null : (err || new Error('no png')));
+    });
 }
 
 const PORT = parseInt(process.env.PORT || '9758', 10);

← 76e878b auto-data-snapshot: 2026-09-26T08:19:11 (1 data files) — dat  ·  back to Model Arena  ·  auto-data-snapshot: 2026-09-26T08:50:16 (3 data files) — dat b0b30ad →