mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 04:25:52 +01:00
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:
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;
|
||||
|
||||
Reference in new issue
Block a user