From c48af2c62bb425f8beba9e2b6e212bdfdd272a35 Mon Sep 17 00:00:00 2001 From: Vikas Singhal Date: Thu, 17 Sep 2026 16:45:08 +0530 Subject: [PATCH] fix(tests): the pause test raced the deferred launch and blocked the deploy (v0.446.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `session-pause-test.cjs`'s HTTP section asserted the row read `running` after unpause. But `launchAgentRuntime` stamps `launching` synchronously and hands the real work to a `setImmediate` whose `.catch` stamps the row `crashed` — and that deferred half does far more than the stubbed `backend.spawn` (env files, skills, credentials). On a box where any of it throws, the status flipped between the `await` and the read. Live: instawp failed `the row is running again`, expresstech passed it — the worst shape a test can have. `make-live.sh` gated correctly and restarted nothing. The section stubs `launchAgentRuntimeNow`: it is about the ROUTE and the status transition, and the launcher has its own tests. Both status assertions now report the value they actually found, so the next failure is diagnosable from the log alone. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 2 +- scripts/session-pause-test.cjs | 12 ++++++++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95039fd9..94edd1a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ new version heading in the same commit. ## [Unreleased] +## [0.446.1] - 2026-09-17 +### Fixed +- **The pause test raced the launcher and blocked the deploy.** `session-pause-test.cjs`'s HTTP section + asserted the row was `running` after unpause, but `launchAgentRuntime` hands the real launch to a + `setImmediate` whose `.catch` stamps the row `crashed` — and that deferred half does far more than the + stubbed `backend.spawn` (env files, skills, credentials). On a box where any of it throws, the status + flipped between the `await` and the read: instawp failed the assertion, expresstech passed it, and + `make-live.sh` correctly refused to restart anything. The section stubs the deferred launch (it is about + the route and the status transition, not the launcher) and both status assertions now report the value + they actually found. + ## [0.446.0] - 2026-09-17 ### Added - **Pause a session.** A new session status, `paused`, that is neither live nor finished: pausing kills the diff --git a/package-lock.json b/package-lock.json index b5a21f7a..fecf137a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agent-os", - "version": "0.446.0", + "version": "0.446.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agent-os", - "version": "0.446.0", + "version": "0.446.1", "license": "MIT", "bin": { "agent-os": "bin/agent-os" diff --git a/package.json b/package.json index 270b5902..62379607 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-os", - "version": "0.446.0", + "version": "0.446.1", "description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.", "license": "MIT", "type": "commonjs", diff --git a/scripts/session-pause-test.cjs b/scripts/session-pause-test.cjs index 1a037a95..0f853510 100644 --- a/scripts/session-pause-test.cjs +++ b/scripts/session-pause-test.cjs @@ -233,6 +233,13 @@ console.log('\n\x1b[1m10) end to end over real HTTP — the routes, the gate and htm.backend.spawn = (_s, o) => { live.add(o.tmuxName); }; htm.backend.capturePane = () => ''; htm.backend.hasClient = () => false; + // Neutralise the DEFERRED half of a launch. `launchAgentRuntime` stamps `launching` synchronously and + // hands the real work to a setImmediate whose `.catch` stamps the row `crashed` — and that work does far + // more than `backend.spawn` (env files, skills, credentials), so on a box where any of it throws the row + // flips out from under the assertion below between the `await` and the read. Live: instawp failed + // `the row is running again` on exactly that race while expresstech passed, which is the worst shape a + // test can have. This section is about the ROUTE and the status transition; the launcher has its own. + htm.launchAgentRuntimeNow = async () => {}; const post = (u, cookie) => fetch(base + u, { method: 'POST', headers: { 'content-type': 'application/json', ...(cookie ? { cookie } : {}) }, body: '{}' }); @@ -250,7 +257,8 @@ console.log('\n\x1b[1m10) end to end over real HTTP — the routes, the gate and const paused = await post(`/api/sessions/${sid}/pause`, cookie).then((r) => r.json()); assert(paused.ok === true, 'an authenticated owner can pause it over HTTP', paused); - assert(haos.db.prepare('SELECT status FROM term_sessions WHERE id = ?').get(sid).status === 'paused', 'the row is paused'); + const statusOf = () => haos.db.prepare('SELECT status FROM term_sessions WHERE id = ?').get(sid).status; + assert(statusOf() === 'paused', 'the row is paused', statusOf()); // The browser terminal must be refused while paused — this is the "readable, never usable" half. const attach = await fetch(base + `/api/sessions/${sid}/attach`, { headers: { cookie } }); @@ -264,7 +272,7 @@ console.log('\n\x1b[1m10) end to end over real HTTP — the routes, the gate and const back = await post(`/api/sessions/${sid}/unpause`, cookie).then((r) => r.json()); assert(back.ok === true, 'unpause succeeds over HTTP', back); - assert(haos.db.prepare('SELECT status FROM term_sessions WHERE id = ?').get(sid).status === 'running', 'and the row is running again'); + assert(statusOf() === 'running', 'and the row is running again', statusOf()); server.close(); try { registry.stopAll(); } catch { /* best effort */ }