From cc91ff04ccacd66cd5c655ffffaca23c1ea74ce9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 22 May 2026 15:53:31 +0100 Subject: [PATCH] fix(release): stabilize config restart QA --- .../config/config-restart-capability-flip.md | 12 ++++++++ src/agents/session-write-lock.test.ts | 30 +++++++++++++++++++ src/agents/session-write-lock.ts | 10 +++++++ 3 files changed, 52 insertions(+) diff --git a/qa/scenarios/config/config-restart-capability-flip.md b/qa/scenarios/config/config-restart-capability-flip.md index 60a33f96c5b..72b0f450fc4 100644 --- a/qa/scenarios/config/config-restart-capability-flip.md +++ b/qa/scenarios/config/config-restart-capability-flip.md @@ -66,6 +66,9 @@ steps: - ref: env - Capability flip - ref: sessionKey + - set: setupStartIndex + value: + expr: state.getSnapshot().messages.length - try: actions: - call: patchConfig @@ -92,6 +95,15 @@ steps: expr: config.setupPrompt timeoutMs: expr: liveTurnTimeoutMs(env, 30000) + - call: waitForOutboundMessage + args: + - ref: state + - lambda: + params: [candidate] + expr: "candidate.conversation.id === 'qa-operator' && String(candidate.text ?? '').includes('Protocol note')" + - expr: liveTurnTimeoutMs(env, 30000) + - sinceIndex: + ref: setupStartIndex - call: readEffectiveTools saveAs: beforeTools args: diff --git a/src/agents/session-write-lock.test.ts b/src/agents/session-write-lock.test.ts index 1d1d3887693..1168d4c78c1 100644 --- a/src/agents/session-write-lock.test.ts +++ b/src/agents/session-write-lock.test.ts @@ -1,3 +1,4 @@ +import fsSync from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -763,6 +764,35 @@ describe("acquireSessionWriteLock", () => { } }); + it("retries when a reported stale same-process lock disappears before recovery", async () => { + await withTempSessionLockFile(async ({ sessionFile, lockPath }) => { + await fs.writeFile( + lockPath, + JSON.stringify({ + pid: process.pid, + createdAt: new Date().toISOString(), + starttime: FAKE_STARTTIME, + }), + "utf8", + ); + let resolverCalls = 0; + testing.setProcessStartTimeResolverForTest((pid) => { + if (pid !== process.pid) { + return null; + } + resolverCalls += 1; + if (resolverCalls === 1) { + fsSync.rmSync(lockPath, { force: true }); + } + return FAKE_STARTTIME; + }); + + const lock = await acquireSessionWriteLock({ sessionFile, timeoutMs: 500 }); + await lock.release(); + expect(resolverCalls).toBeGreaterThan(0); + }); + }); + it("removes held locks on termination signals", async () => { const signals = ["SIGINT", "SIGTERM", "SIGQUIT", "SIGABRT"] as const; const originalKill = process.kill.bind(process); diff --git a/src/agents/session-write-lock.ts b/src/agents/session-write-lock.ts index 8d28117dde2..8fc67c2919c 100644 --- a/src/agents/session-write-lock.ts +++ b/src/agents/session-write-lock.ts @@ -560,6 +560,16 @@ async function removeReportedStaleLockIfStillStale(params: { }): Promise { const nowMs = Date.now(); const payload = await readLockPayload(params.lockPath); + if (payload === null) { + try { + await fs.access(params.lockPath); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return true; + } + throw error; + } + } const inspected = inspectLockPayloadForSession({ payload, staleMs: params.staleMs,