Skip to content

Commit 4c6dd7e

Browse files
fix(clawsweeper): address review for automerge-openclaw-openclaw-82891 (2)
1 parent fd2fe46 commit 4c6dd7e

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

src/agents/pi-embedded-runner/run/attempt.session-lock.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,46 @@ describe("embedded attempt session lock lifecycle", () => {
7878
expect(events).toEqual(["prep-release", "post-write", "post-release"]);
7979
});
8080

81+
it("reuses its active post-prompt lock for nested session writes", async () => {
82+
const events: string[] = [];
83+
const sessionFile = await createTempSessionFile();
84+
const acquireSessionWriteLock = vi
85+
.fn()
86+
.mockResolvedValueOnce({ release: vi.fn(async () => events.push("prep-release")) })
87+
.mockResolvedValueOnce({ release: vi.fn(async () => events.push("post-release")) })
88+
.mockRejectedValueOnce(
89+
new SessionWriteLockTimeoutError({
90+
timeoutMs: lockOptions.timeoutMs,
91+
owner: "pid=789",
92+
lockPath: `${sessionFile}.lock`,
93+
}),
94+
);
95+
96+
const controller = await createEmbeddedAttemptSessionLockController({
97+
acquireSessionWriteLock,
98+
lockOptions: { ...lockOptions, sessionFile },
99+
});
100+
101+
await controller.releaseForPrompt();
102+
await controller.withSessionWriteLock(async () => {
103+
events.push("outer-start");
104+
await fs.appendFile(sessionFile, '{"type":"message","id":"local"}\n', "utf8");
105+
await controller.withSessionWriteLock(async () => {
106+
events.push("inner-write");
107+
});
108+
events.push("outer-end");
109+
});
110+
111+
expect(acquireSessionWriteLock).toHaveBeenCalledTimes(2);
112+
expect(events).toEqual([
113+
"prep-release",
114+
"outer-start",
115+
"inner-write",
116+
"outer-end",
117+
"post-release",
118+
]);
119+
});
120+
81121
it("drains queued Pi session events before reacquiring for cleanup", async () => {
82122
const events: string[] = [];
83123
let resolveQueue!: () => void;

src/agents/pi-embedded-runner/run/attempt.session-lock.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AsyncLocalStorage } from "node:async_hooks";
12
import fs from "node:fs/promises";
23
import { isSessionWriteLockTimeoutError } from "../../session-write-lock-error.js";
34
import type { acquireSessionWriteLock } from "../../session-write-lock.js";
@@ -257,6 +258,7 @@ export async function createEmbeddedAttemptSessionLockController(params: {
257258
});
258259

259260
let heldLock: SessionLock | undefined = await acquireLock();
261+
const activeWriteLock = new AsyncLocalStorage<SessionLock>();
260262
let fenceFingerprint: SessionFileFingerprint | undefined;
261263
let fenceActive = false;
262264
let takeoverDetected = false;
@@ -310,12 +312,21 @@ export async function createEmbeddedAttemptSessionLockController(params: {
310312
if (takeoverDetected) {
311313
throw new EmbeddedAttemptSessionTakeoverError(params.lockOptions.sessionFile);
312314
}
315+
if (activeWriteLock.getStore()) {
316+
return await run();
317+
}
313318
const { lock, owned } = await acquireWriteLock();
314319
try {
315320
await assertSessionFileFence();
316-
const result = await run();
317-
await refreshSessionFileFence();
318-
return result;
321+
const runWithLock = async () => {
322+
const result = await run();
323+
await refreshSessionFileFence();
324+
return result;
325+
};
326+
if (owned) {
327+
return await activeWriteLock.run(lock, runWithLock);
328+
}
329+
return await runWithLock();
319330
} finally {
320331
if (owned) {
321332
await lock.release();

0 commit comments

Comments
 (0)