From be82a912b2f82af270c5c0a499edb1025dada953 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 12 Apr 2026 20:43:50 -0500 Subject: [PATCH] Fix signoff e2e for auto-checked out issues --- tests/e2e/signoff-policy.spec.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/e2e/signoff-policy.spec.ts b/tests/e2e/signoff-policy.spec.ts index 934eeaf9..a2cde2c3 100644 --- a/tests/e2e/signoff-policy.spec.ts +++ b/tests/e2e/signoff-policy.spec.ts @@ -42,6 +42,12 @@ interface TestContext { issueIds: string[]; } +interface IssueRunLockState { + assigneeAgentId: string | null; + checkoutRunId: string | null; + executionRunId: string | null; +} + /** Create an authenticated APIRequestContext for an agent (token set, no run ID yet). */ async function createAgentRequest(token: string): Promise { return pwRequest.newContext({ @@ -58,6 +64,17 @@ async function invokeHeartbeat(board: APIRequestContext, agentId: string): Promi return run.id; } +async function getIssueRunLockState(board: APIRequestContext, issueId: string): Promise { + const res = await board.get(`${BASE_URL}/api/issues/${issueId}`); + expect(res.ok()).toBe(true); + const issue = await res.json(); + return { + assigneeAgentId: issue.assigneeAgentId ?? null, + checkoutRunId: issue.checkoutRunId ?? null, + executionRunId: issue.executionRunId ?? null, + }; +} + /** PATCH an issue as an agent with a fresh heartbeat run ID. */ async function agentPatch( board: APIRequestContext, @@ -88,6 +105,17 @@ async function agentCheckoutAndPatch( data: { agentId: agent.agentId, expectedStatuses }, }); if (!checkoutRes.ok()) { + if (checkoutRes.status() === 409) { + const issueRunLock = await getIssueRunLockState(board, issueId); + const lockedRunId = issueRunLock.checkoutRunId ?? issueRunLock.executionRunId; + const res = await agent.request.patch(`${BASE_URL}/api/issues/${issueId}`, { + headers: { "X-Paperclip-Run-Id": lockedRunId ?? runId }, + data: patchData, + }); + if (res.ok() && issueRunLock.assigneeAgentId === agent.agentId) { + return res; + } + } // If agent checkout fails (e.g. run expired), fall back to board checkout // then PATCH with the agent's identity const boardCheckout = await board.post(`${BASE_URL}/api/issues/${issueId}/checkout`, {