From f031eb0b9f259f765353d4dd603210b41149100b Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Fri, 28 Aug 2026 09:38:40 +0500 Subject: [PATCH] Fix: write the build-path flip before answering the POST serve-question answered POST /build-path with 200 and only then wrote the flip file. The caller is a separate process, so the response could reach it while the server was still preempted before the write landed: a poller that trusted the 200 could look for the flip file and miss it. Measured on a loaded machine, the old order lost that race 29 times out of 40; writing first and answering after loses it 0 times out of 40. This is what made tests/serve-question.test.mjs fail intermittently in CI on the Node 22 job while passing on Node 24. AI-assisted change: diagnosed and implemented with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 --- skill/scripts/serve-question.mjs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/skill/scripts/serve-question.mjs b/skill/scripts/serve-question.mjs index e08052c48..689c9559a 100644 --- a/skill/scripts/serve-question.mjs +++ b/skill/scripts/serve-question.mjs @@ -1667,19 +1667,24 @@ const server = http.createServer((req, res) => { let body = ''; req.on('data', (chunk) => { body += chunk; }); req.on('end', () => { - res.writeHead(200, { 'content-type': 'application/json' }); - res.end('{"ok":true}'); let value = null; try { value = JSON.parse(body).value; } catch { /* ignore */ } - if (value !== 'comp' && value !== 'code') return; - const wasComp = liveBuildPath === 'comp'; - liveBuildPath = value; - // Only a flip TO comp needs the agent mid-round: comps must start - // rendering into the declared slots. The reverse is free. - if (detachedKey && value === 'comp' && !wasComp) { - fs.mkdirSync(QUESTION_DIR, { recursive: true }); - fs.writeFileSync(flipFile(detachedKey), JSON.stringify({ buildPath: 'comp' }) + '\n'); + if (value === 'comp' || value === 'code') { + const wasComp = liveBuildPath === 'comp'; + liveBuildPath = value; + // Only a flip TO comp needs the agent mid-round: comps must start + // rendering into the declared slots. The reverse is free. + if (detachedKey && value === 'comp' && !wasComp) { + fs.mkdirSync(QUESTION_DIR, { recursive: true }); + fs.writeFileSync(flipFile(detachedKey), JSON.stringify({ buildPath: 'comp' }) + '\n'); + } } + // Answer only once the flip is on disk. Responding first raced the + // caller: the 200 reached the client (a separate process) while this + // one could still be preempted before the write landed, so a poller + // that trusted the 200 could look for the flip file and miss it. + res.writeHead(200, { 'content-type': 'application/json' }); + res.end('{"ok":true}'); }); return; }