From b9e2a0dce7e694f66d82034741334f7a58adf4b4 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:41:49 +0000 Subject: [PATCH] Worker dashboard: a flock-held slot with an empty file counts as held (label unknown), not free Seen on igneum-build-1 at 10:39Z on 7 October 2026: both slot files flock-held and empty while two suites ran and three waiters queued, so the header read 0/2. The holder's missing line is the build-server agent's to fix; the collector no longer reports a full box as free either way, and the page shows "held, no holder line" in the lane. Co-Authored-By: Claude Fable 5.1 --- tools/workers/lib.mjs | 1 + tools/workers/page/workers.html | 1 + tools/workers/test/parsers.test.mjs | 3 +++ 3 files changed, 5 insertions(+) diff --git a/tools/workers/lib.mjs b/tools/workers/lib.mjs index 6933e914..52063e9f 100644 --- a/tools/workers/lib.mjs +++ b/tools/workers/lib.mjs @@ -130,6 +130,7 @@ export function parseLockDir(files, { day, now, aliveFn } = {}) { const alive = line && aliveFn ? aliveFn(line.pid) : null; const isHeld = f.held ?? (line ? alive !== false : false); if (line && isHeld) held.push({ slot: f.name, ...line, kind: deriveKind(line) }); + else if (f.held === true) held.push({ slot: f.name, pid: null, since: null, waited_s: null, label: null, agent: null, worktree: null, crate: null, command: null, target: null, kind: null, nice: null, cores: null, no_line: true }); // the flock is taken but the holder wrote no line (7 Oct 2026: the scheduling-class path on build-1) else free.push(f.name); } held.sort((a, b) => a.slot.localeCompare(b.slot)); diff --git a/tools/workers/page/workers.html b/tools/workers/page/workers.html index d668e6f5..ae026e32 100644 --- a/tools/workers/page/workers.html +++ b/tools/workers/page/workers.html @@ -244,6 +244,7 @@ function renderCrew() { function nowItems() { const out = []; for (const bx of boxesOf(D)) for (const r of bx.running || []) out.push({ where: bx.name, slot: r.slot, title: `${kindLabel(r.kind)}`, sub: `${r.worktree || '?'}/${r.crate || ''}${r.agent ? ' · ' + r.agent : ''}`, cmd: r.command, started_at: r.started_at, kind: r.kind, extra: [r.target || '', classLine(r)].filter(Boolean).join(' · ') }); + for (const bx of boxesOf(D)) for (const h of (bx.slots && bx.slots.held || []).filter(h => h.no_line && !(bx.running || []).some(r => r.slot === h.slot))) out.push({ where: bx.name, slot: h.slot, title: 'held, no holder line', sub: 'the slot is flock-held but its file is empty (the holder wrote no line)', cmd: '', started_at: null, kind: null, extra: '' }); for (const h of (D.mac && D.mac.slots.held) || []) out.push({ where: D.mac.name, slot: h.slot, title: h.slot === 'measure' ? 'measurement' : h.slot.startsWith('run') ? 'functional run' : kindLabel(h.kind), sub: h.worktree || 'this Mac', cmd: h.command || h.label, started_at: h.since, kind: h.kind, extra: h.waited_s ? `waited ${fmtDur(h.waited_s)}` : '' }); for (const pc of (D.pcs && D.pcs.machines) || []) if (pc.running) out.push({ where: pc.name, slot: 'relay job', title: `${pc.running.kind} ${pc.running.job}`, sub: pc.running.title || pc.machine || '', cmd: pc.running.stage ? `stage ${pc.running.stage}` : 'reports every 5 min while it runs', started_at: pc.running.started_at, kind: pc.running.kind, extra: pc.running.last_report_at ? `last report ${ago(pc.running.last_report_at)}` : '' }); return out; diff --git a/tools/workers/test/parsers.test.mjs b/tools/workers/test/parsers.test.mjs index c8c3dc79..af11b1fc 100644 --- a/tools/workers/test/parsers.test.mjs +++ b/tools/workers/test/parsers.test.mjs @@ -72,6 +72,9 @@ test('lock dir fixture: one held slot, one stale line, one empty, count from slo // an explicit flock verdict overrides the pid check const r2 = parseLockDir(files.map(f => ({ ...f, held: f.name === 'build-1' })), { day: '2026-10-06', now: '2026-10-06T18:00:00Z' }); assert.deepEqual(r2.held.map(h => h.slot), ['build-1']); + // a flock-held slot whose file is empty is still held (7 Oct 2026 on build-1: two held, both empty, the box full) + const r3 = parseLockDir([{ name: 'build-2', text: '', held: true }, { name: 'build-0', text: '', held: false }, { name: 'slots', text: '2' }], { now: '2026-10-07T10:39:00Z' }); + assert.equal(r3.held.length, 1); assert.equal(r3.held[0].slot, 'build-2'); assert.equal(r3.held[0].no_line, true); assert.deepEqual(r3.free, ['build-0']); }); test('builds JSONL fixture: five rows, one bad line, newest first, derived fields', () => {