fix(native): reclaim task execution before same-run recovery

Restore the original task execution binding atomically during native same-run recovery. Preserve current owners, stops, pauses, assignments, and dependency waits.

Isolate candidate failures, bound deferral writes, prioritize undated retries, and allow recovery ahead of queued waiters without premature queue promotion. Add PostgreSQL attachment, ownership, contention, fairness, and queue regressions.

Verification: 99 focused tests, workspace typecheck, build, full current-head CI, and Apex 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-15 01:53:26 -07:00
1 parent cd77c1c4dc
commit 307c27e3d8
5 files changed
+370 -32

No files matched your search

@@ -95,6 +95,9 @@ describe("P6-25 pre-result native session recovery", () => {
const bootstrapRetryRunId = "79000000-0000-4000-8000-000000000009";
const observedExpiredRunId = "79000000-0000-4000-8000-000000000010";
const observedLivePidRunId = "79000000-0000-4000-8000-000000000011";
const fixtureRunIds = [runId, cancelledRunId, exhaustedRunId, missingCheckpointRunId,
initialRunId, bootstrapRetryRunId, observedExpiredRunId, observedLivePidRunId];
const issueForRun = (id: string) => id === runId ? issueId : id;
const persistedProfile = {
mode: "native",
nativeExecutionInput: { schema: "paperclip.native-execution-input.v1", binding: { runId } },
@@ -118,14 +121,10 @@ describe("P6-25 pre-result native session recovery", () => {
adapterType: "codex_local",
status: "running",
});
await db.insert(issues).values({
id: issueId,
companyId,
title: "Resume the same native run",
status: "in_progress",
assigneeAgentId: agentId,
workMode: "standard",
});
await db.insert(issues).values(fixtureRunIds.map(id => ({
id: issueForRun(id), companyId, title: "Resume the same native run",
status: "in_progress", assigneeAgentId: agentId, workMode: "standard" as const,
})));
await db.insert(heartbeatRuns).values([
{
id: runId,
@@ -230,7 +229,10 @@ describe("P6-25 pre-result native session recovery", () => {
},
contextSnapshot: { issueId },
})),
]);
].map(run => ({ ...run, runtimeMode: "native" as const,
nativeIssueId: issueForRun(run.id), contextSnapshot: { issueId: issueForRun(run.id) },
})));
await db.insert(nativeRunFinalizations).values([
{ runId, companyId, issueId, phase: "retryable_failure", attempt: 1, nextAttemptAt: new Date(0) },
{ runId: cancelledRunId, companyId, issueId, phase: "retryable_failure", attempt: 1, nextAttemptAt: new Date(0) },
@@ -263,7 +265,10 @@ describe("P6-25 pre-result native session recovery", () => {
leaseOwner: "prior-native-owner",
leaseExpiresAt: new Date(0),
})),
]);
].map(value => ({ ...value, issueId: issueForRun(value.runId),
phase: value.phase as typeof nativeRunFinalizations.$inferInsert.phase,
})));
}, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS);
afterAll(async () => temporary?.cleanup());
@@ -276,12 +281,15 @@ describe("P6-25 pre-result native session recovery", () => {
);
const eligibleRunId = "79100000-0000-4000-8000-000000000099";
const candidateRunIds = [...heldRunIds, eligibleRunId];
await db.insert(issues).values(candidateRunIds.map(id => ({
id, companyId, title: "Independent recovery candidate", status: "in_progress", assigneeAgentId: agentId,
})));
await db.insert(heartbeatRuns).values(
candidateRunIds.map((candidateRunId) => ({
id: candidateRunId,
companyId,
agentId,
nativeIssueId: issueId,
nativeIssueId: candidateRunId,
status: "running",
runtimeMode: "native",
nativePhase:
@@ -309,7 +317,7 @@ describe("P6-25 pre-result native session recovery", () => {
candidateRunIds.map((candidateRunId) => ({
runId: candidateRunId,
companyId,
issueId,
issueId: candidateRunId,
phase: "retryable_failure",
attempt: 1,
nextAttemptAt: new Date(0),
@@ -341,12 +349,14 @@ describe("P6-25 pre-result native session recovery", () => {
),
).toBe(true);
} finally {
await db.delete(heartbeatRunEvents).where(inArray(heartbeatRunEvents.runId, candidateRunIds));
await db
.delete(nativeRunFinalizations)
.where(inArray(nativeRunFinalizations.runId, candidateRunIds));
await db
.delete(heartbeatRuns)
.where(inArray(heartbeatRuns.id, candidateRunIds));
await db.delete(issues).where(inArray(issues.id, candidateRunIds));
}
});
@@ -523,7 +533,7 @@ describe("P6-25 pre-result native session recovery", () => {
expect.objectContaining({ phase: "terminal_failure", failureCode: "native_session_interrupted" }),
]);
await expect(db.select().from(issueRecoveryActions)
.where(eq(issueRecoveryActions.sourceIssueId, issueId))).resolves.toEqual([
.where(eq(issueRecoveryActions.sourceIssueId, missingCheckpointRunId))).resolves.toEqual([
expect.objectContaining({ cause: "native_session_interrupted", wakePolicy: null }),
]);
});
@@ -542,11 +552,13 @@ describe("P6-25 pre-result native session recovery", () => {
it("sends exactly one Sentry event when a sweep resolves a run from running to failed", async () => {
const freshRunId = "79000000-0000-4000-8000-000000000101";
await db.insert(issues).values({ id: freshRunId, companyId, title: "Independent failure report",
status: "in_progress", assigneeAgentId: agentId });
await db.insert(heartbeatRuns).values({
id: freshRunId,
companyId,
agentId,
nativeIssueId: issueId,
nativeIssueId: freshRunId,
status: "running",
runtimeMode: "native",
runtimeModeResolvedAt: new Date(),
@@ -556,12 +568,12 @@ describe("P6-25 pre-result native session recovery", () => {
binding: { runId: freshRunId },
},
},
contextSnapshot: { issueId },
contextSnapshot: { issueId: freshRunId },
});
await db.insert(nativeRunFinalizations).values({
runId: freshRunId,
companyId,
issueId,
issueId: freshRunId,
phase: "retryable_failure",
attempt: 1,
});
@@ -578,11 +590,13 @@ describe("P6-25 pre-result native session recovery", () => {
it("sends no Sentry event when a sweep finds a run that is already failed", async () => {
const freshRunId = "79000000-0000-4000-8000-000000000102";
await db.insert(issues).values({ id: freshRunId, companyId, title: "Independent failure report",
status: "in_progress", assigneeAgentId: agentId });
await db.insert(heartbeatRuns).values({
id: freshRunId,
companyId,
agentId,
nativeIssueId: issueId,
nativeIssueId: freshRunId,
status: "failed",
runtimeMode: "native",
runtimeModeResolvedAt: new Date(),
@@ -592,12 +606,12 @@ describe("P6-25 pre-result native session recovery", () => {
binding: { runId: freshRunId },
},
},
contextSnapshot: { issueId },
contextSnapshot: { issueId: freshRunId },
});
await db.insert(nativeRunFinalizations).values({
runId: freshRunId,
companyId,
issueId,
issueId: freshRunId,
phase: "retryable_failure",
attempt: 1,
});