mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-08 22:13:59 +01:00
fix(server): keep a successor's issue lock when a finalizing native run releases its checkout (#19156)
`release_checkout` now clears `checkoutRunId` and the execution lock only when they belong to the committing run, so a successor that claimed the issue while its predecessor was finalizing keeps its claim and passes reviewed-chat attestation. `attestReviewedExternalChatRun` reports the first failed check as a closed label. Setup failures store it in `resultJson.reviewedChatBinding.denialReason`, and Sentry run-failure diagnostics export it as `execution.reviewedChatBindingDenialReason` (no IDs or text). The error message is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6fa54d5577
commit
7edbc1fc07
9 files changed
+271
-34
No files matched your search
@@ -1540,13 +1540,17 @@ async function materializeDecisionEffect(input: {
|
||||
}
|
||||
if (effect.kind === "release_checkout") {
|
||||
await notifyUnsafeWorkspaceExportRecovery(input.tx, { companyId: input.companyId, issueId: input.issue.id });
|
||||
// Release only this run's own locks. A successor can claim execution
|
||||
// without changing statusVersion, so a preserve decision committed after
|
||||
// that claim must not clear it. Each SET reads the pre-update row.
|
||||
const ownsExecution = sql`${issues.executionRunId} = ${input.runId}::uuid`;
|
||||
const [released] = await input.tx
|
||||
.update(issues)
|
||||
.set({
|
||||
checkoutRunId: null,
|
||||
executionRunId: null,
|
||||
executionAgentNameKey: null,
|
||||
executionLockedAt: null,
|
||||
checkoutRunId: sql`case when ${issues.checkoutRunId} = ${input.runId}::uuid then null else ${issues.checkoutRunId} end`,
|
||||
executionRunId: sql`case when ${ownsExecution} then null else ${issues.executionRunId} end`,
|
||||
executionAgentNameKey: sql`case when ${ownsExecution} then null else ${issues.executionAgentNameKey} end`,
|
||||
executionLockedAt: sql`case when ${ownsExecution} then null else ${issues.executionLockedAt} end`,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(
|
||||
@@ -1555,13 +1559,20 @@ async function materializeDecisionEffect(input: {
|
||||
eq(issues.companyId, input.companyId),
|
||||
),
|
||||
)
|
||||
.returning({ id: issues.id });
|
||||
.returning({
|
||||
id: issues.id,
|
||||
checkoutRunId: issues.checkoutRunId,
|
||||
executionRunId: issues.executionRunId,
|
||||
});
|
||||
if (!released) throw new Error("native_checkout_not_released");
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "issue_checkout",
|
||||
targetId: released.id,
|
||||
payload: { checkoutRunId: null, executionRunId: null },
|
||||
payload: {
|
||||
checkoutRunId: released.checkoutRunId,
|
||||
executionRunId: released.executionRunId,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (effect.kind === "record_recovery") {
|
||||
|
||||
Reference in new issue
Block a user