Handle harness checkout conflicts gracefully

This commit is contained in:
Dotta 2026-04-12 20:57:31 -05:00
parent be82a912b2
commit 8e82ac7e38
2 changed files with 13 additions and 3 deletions

View file

@ -574,4 +574,5 @@ describe("heartbeat comment wake batching", () => {
await gateway.close();
}
}, 20_000);
});

View file

@ -17,7 +17,7 @@ import {
projects,
projectWorkspaces,
} from "@paperclipai/db";
import { conflict, notFound } from "../errors.js";
import { conflict, HttpError, notFound } from "../errors.js";
import { logger } from "../middleware/logger.js";
import { publishLiveEvent } from "./live-events.js";
import { getRunLogStore, type RunLogHandle } from "./run-log-store.js";
@ -787,6 +787,10 @@ function shouldAutoCheckoutIssueForWake(input: {
return true;
}
function isCheckoutConflictError(error: unknown): boolean {
return error instanceof HttpError && error.status === 409 && error.message === "Issue checkout conflict";
}
function deriveCommentId(
contextSnapshot: Record<string, unknown> | null | undefined,
payload: Record<string, unknown> | null | undefined,
@ -2704,8 +2708,13 @@ export function heartbeatService(db: Db) {
agentId: agent.id,
})
) {
await issuesSvc.checkout(issueId, agent.id, ["todo", "backlog", "blocked"], run.id);
context[PAPERCLIP_HARNESS_CHECKOUT_KEY] = true;
try {
await issuesSvc.checkout(issueId, agent.id, ["todo", "backlog", "blocked"], run.id);
context[PAPERCLIP_HARNESS_CHECKOUT_KEY] = true;
} catch (error) {
if (!isCheckoutConflictError(error)) throw error;
context[PAPERCLIP_HARNESS_CHECKOUT_KEY] = false;
}
issueContext = await getIssueExecutionContext(agent.companyId, issueId);
}
const issueAssigneeOverrides =