fix: recover deferred messages and completed unblock waits (#16673)

Recover accepted deferred messages through the latest finished issue run and
recognize exact completed agent unblock notifications as passive idle waits.

Preserve assignment handoffs, pause and Stop, failed outcomes, and cleanup
ownership. Honor local and SSH release contracts and atomically settle cleanup
claims. Add PostgreSQL recovery, concurrency, and interruption regressions.

Validation: recursive typecheck and build pass; 678 focused tests pass; full CI
passes; Apex review is 5/5 with no unresolved findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-17 16:53:29 -07:00
1 parent 27ab902d3d
commit 391dd15ff2
19 files changed
+546 -62

No files matched your search

@@ -235,7 +235,7 @@ export interface WakeQueueTransaction {
*/
export interface IssueLockWriter {
withIssueExecutionLock(
input: { companyId: string; runId: string; now: Date },
input: { companyId: string; runId: string; now: Date; resumeStrandedQueue?: boolean },
fn: (
locked: LockedIssueExecution,
ports: { host: WakeQueueHost; transaction: WakeQueueTransaction },
@@ -120,6 +120,7 @@ export type ReleaseIssueExecutionInput = {
runId: string;
now: Date;
suppressImmediateRecovery?: boolean;
resumeStrandedQueue?: boolean;
};
type PauseHoldFacts = Awaited<ReturnType<WakeQueueTransaction["getPauseHoldFacts"]>>;
@@ -200,7 +201,7 @@ async function runReleaseDrain(
!candidate.preservesIndependentContinuation && candidate.queuedCommentIds.length > 0 &&
["issue_commented", "issue_reopened_via_comment"].includes(candidate.wakeReason ?? candidate.reason ?? "");
if (ordinaryTaskComment && candidate.agentId !== issue.assigneeAgentId) {
if (run.agentId !== issue.assigneeAgentId) {
if (input.resumeStrandedQueue || run.agentId !== issue.assigneeAgentId) {
// The old owner can release before assignment admission adopts these
// exact IDs. Leave its receipt intact, skip it for this drain, and let
// a current-assignee wake behind it proceed.
@@ -968,7 +969,8 @@ export function createReleaseIssueExecution(deps: {
input: ReleaseIssueExecutionInput,
): Promise<{ outcome: ReleaseOutcome; postCommitEffects: PostCommitEffect[] }> {
const result = await deps.issueLock.withIssueExecutionLock(
{ companyId: input.companyId, runId: input.runId, now: input.now },
{ companyId: input.companyId, runId: input.runId, now: input.now,
...(input.resumeStrandedQueue ? { resumeStrandedQueue: true } : {}) },
(locked, ports) => runReleaseDrain(locked, ports, input),
);