fix(agents): release session lock on manual abort

This commit is contained in:
Peter Steinberger
2026-05-31 13:58:40 +01:00
parent 5a0e67791f
commit 56fa5420d6
3 changed files with 69 additions and 5 deletions

View File

@@ -0,0 +1,44 @@
import { describe, expect, it, vi } from "vitest";
import { releaseEmbeddedAttemptSessionLockForAbort } from "./attempt-abort.js";
describe("releaseEmbeddedAttemptSessionLockForAbort", () => {
it("releases the retained session lock for manual aborts", async () => {
const releaseHeldLockForAbort = vi.fn(async () => {});
const warn = vi.fn();
releaseEmbeddedAttemptSessionLockForAbort({
sessionLockController: { releaseHeldLockForAbort },
log: { warn },
runId: "run-manual",
abortKind: "abort",
});
await Promise.resolve();
expect(releaseHeldLockForAbort).toHaveBeenCalledTimes(1);
expect(warn).not.toHaveBeenCalled();
});
it("logs release failures without throwing from the abort path", async () => {
const releaseError = new Error("locked");
const releaseHeldLockForAbort = vi.fn(async () => {
throw releaseError;
});
const warn = vi.fn();
releaseEmbeddedAttemptSessionLockForAbort({
sessionLockController: { releaseHeldLockForAbort },
log: { warn },
runId: "run-timeout",
abortKind: "timeout abort",
});
await Promise.resolve();
await Promise.resolve();
expect(releaseHeldLockForAbort).toHaveBeenCalledTimes(1);
expect(warn).toHaveBeenCalledWith(
"failed to release session lock on timeout abort: runId=run-timeout Error: locked",
);
});
});

View File

@@ -0,0 +1,18 @@
import type { EmbeddedAttemptSessionLockController } from "./attempt.session-lock.js";
type AbortLockReleaseLog = {
warn(message: string): void;
};
export function releaseEmbeddedAttemptSessionLockForAbort(params: {
sessionLockController: Pick<EmbeddedAttemptSessionLockController, "releaseHeldLockForAbort">;
log: AbortLockReleaseLog;
runId: string;
abortKind: "abort" | "timeout abort";
}): void {
void params.sessionLockController.releaseHeldLockForAbort().catch((err) => {
params.log.warn(
`failed to release session lock on ${params.abortKind}: runId=${params.runId} ${String(err)}`,
);
});
}

View File

@@ -308,6 +308,7 @@ import {
rotateTranscriptAfterCompaction,
shouldRotateCompactionTranscript,
} from "../compaction-successor-transcript.js";
import { releaseEmbeddedAttemptSessionLockForAbort } from "./attempt-abort.js";
import { resolveAttemptWorkspaceBootstrapRouting } from "./attempt-bootstrap-routing.js";
import { configureEmbeddedAttemptHttpRuntime } from "./attempt-http-runtime.js";
import {
@@ -2984,12 +2985,13 @@ export async function runEmbeddedAttempt(
sessionFile: params.sessionFile,
reason: "timeout",
});
void sessionLockController.releaseHeldLockForAbort().catch((err) => {
log.warn(
`failed to release session lock on timeout abort: runId=${params.runId} ${String(err)}`,
);
});
}
releaseEmbeddedAttemptSessionLockForAbort({
sessionLockController,
log,
runId: params.runId,
abortKind: isTimeout ? "timeout abort" : "abort",
});
};
abortRunForExternalSignal = abortRun;
const idleTimeoutTrigger: ((error: Error) => void) | undefined = (error) => {