fix(server): fence native retry cancellation through final commit

Recheck retry eligibility under the coordinator lock before creating a cancellation intent. Preserve later run and coordinator outcomes with a failed-only acknowledged-result CAS, while retaining same-intent recovery and NOWAIT conflict handling.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
(cherry picked from commit 29230e2000)
This commit is contained in:
Dotta committed 2026-09-30 13:54:30 -05:00
1 parent 45b977bca0
commit e4bf459e31
5 files changed
+186 -34

No files matched your search

@@ -1,4 +1,4 @@
import { assertCancellationRequest, cancellationIntentId as callerCancellationIntentId, cancellationRequestId } from "./native-cancellation-request.js";
import { nativeRetryCancellationEligible, rethrowNativeCancellationLockConflict, assertCancellationRequest, cancellationIntentId as callerCancellationIntentId, cancellationRequestId } from "./native-cancellation-request.js";
import { readNativeCursorPlanWait } from "./native-cursor-plan-wait.js";
import { resolveAcpxQualification } from "./acpx-qualification.js";
import { readLocalAiCredentialFile } from "../local-ai-credential-file.js";
@@ -157,7 +157,7 @@ import {
type NativeAuthoritativeIssueStatus,
type NativeStatusDecision,
} from "./status-arbiter.js";
import { HttpError } from "../../errors.js";
import { conflict, HttpError } from "../../errors.js";
import { redactSensitiveText } from "../../redaction.js";
import { resolvePaperclipRunnerBinary } from "./native-codex-runner.js";
import {
@@ -6757,8 +6757,10 @@ export async function cancelNativeSession(
}
if (isNativeRunnerOwnershipHeld(lockedRun))
throw new NativeRunnerOwnershipUnverifiedError();
const coordinator = await tx
.select({ runId: nativeRunFinalizations.runId })
const retryCancellation = lockedRun.status === "failed"
|| record(record(lockedRun.resultJson).startupCancellation).retryCancellation === true;
const coordinatorQuery = tx
.select({ runId: nativeRunFinalizations.runId, phase: nativeRunFinalizations.phase, failureCode: nativeRunFinalizations.failureCode })
.from(nativeRunFinalizations)
.where(
and(
@@ -6766,9 +6768,12 @@ export async function cancelNativeSession(
eq(nativeRunFinalizations.companyId, cancellationContext.companyId),
eq(nativeRunFinalizations.issueId, cancellationContext.issueId),
),
)
.limit(1)
.then((rows) => rows[0] ?? null);
);
// Recheck after reservation, at the same transaction that records intent
// and disables the retry. NOWAIT avoids coordinator -> run lock inversion.
const coordinator = await (retryCancellation
? coordinatorQuery.for("update", { noWait: true }) : coordinatorQuery)
.limit(1).then((rows) => rows[0] ?? null);
if (!coordinator)
throw new Error("native_cancellation_coordinator_missing");
@@ -6778,6 +6783,10 @@ export async function cancelNativeSession(
const requestedId = options.cancellationRequestId
?? cancellationRequestId(record(resultJson.startupCancellation).cancellationRequestId);
if (requestedId) assertCancellationRequest(resultJson, requestedId);
if (retryCancellation && !nativeRetryCancellationEligible({
runId, companyId: cancellationContext.companyId, issueId: cancellationContext.issueId,
resultJson, requestId: requestedId, scope: options.scope ?? "run",
}, coordinator)) throw conflict("Native retry is no longer cancellable");
const existing = record(resultJson.nativeCancellation);
const existingIntentId =
typeof existing.intentId === "string" && existing.intentId.length > 0
@@ -6907,7 +6916,7 @@ export async function cancelNativeSession(
priorCoordinatorDecisionId: cancellationContext.coordinatorDecisionId,
existing: false,
};
});
}).catch(rethrowNativeCancellationLockConflict);
if (intentPublication) publishActivity(intentPublication);
cancellationIntentId = intent.intentId;
auditId = intent.auditId;