From 17223a9b251a75ed84e8f813b120d591c380ebdf Mon Sep 17 00:00:00 2001 From: Lourenco Castro Date: Fri, 11 Sep 2026 14:19:48 +0200 Subject: [PATCH 1/2] fix(recovery): use normal lane for missing-comment retries Missing-comment retries may need to complete deliverable work such as routine document updates. Scrub status-only recovery guards from both the queued run and wake payload while preserving the write gate for true status-only recovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../heartbeat-comment-wake-batching.test.ts | 27 ++++++++++++++++--- server/src/services/heartbeat.ts | 6 +++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/server/src/__tests__/heartbeat-comment-wake-batching.test.ts b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts index d8898a1f0a..0a06dd7883 100644 --- a/server/src/__tests__/heartbeat-comment-wake-batching.test.ts +++ b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts @@ -3211,7 +3211,16 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { expect(runs[1]?.contextSnapshot).toMatchObject({ retryReason: "missing_issue_comment", }); - expect(runs[1]?.contextSnapshot).not.toHaveProperty("modelProfile"); + for (const key of [ + "modelProfile", + "paperclipModelProfile", + "recoveryIntent", + "allowDeliverableWork", + "allowDocumentUpdates", + "resumeRequiresNormalModel", + ]) { + expect(runs[1]?.contextSnapshot).not.toHaveProperty(key); + } } finally { gateway.releaseFirstWait(); await gateway.close(); @@ -3416,9 +3425,19 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { ), ); expect(missingCommentRetries).toHaveLength(1); - expect(missingCommentRetries[0]?.payload).not.toHaveProperty( - "modelProfile", - ); + expect(missingCommentRetries[0]?.payload).toMatchObject({ + retryReason: "missing_issue_comment", + }); + for (const key of [ + "modelProfile", + "paperclipModelProfile", + "recoveryIntent", + "allowDeliverableWork", + "allowDocumentUpdates", + "resumeRequiresNormalModel", + ]) { + expect(missingCommentRetries[0]?.payload).not.toHaveProperty(key); + } } finally { gateway.releaseFirstWait(); await gateway.close(); diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index c4143c93c6..9a0a822e82 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -13388,6 +13388,8 @@ export function heartbeatService( const contextSnapshot = parseObject(run.contextSnapshot); const taskKey = deriveTaskKeyWithHeartbeatFallback(contextSnapshot, null); const sessionBefore = await resolveSessionBeforeForWakeup(agent, taskKey); + // Missing-comment retries continue the original deliverable work, so they + // must not inherit the status-only mutation guards. const retryContextSnapshot = withRecoveryContext( { ...contextSnapshot, @@ -13396,7 +13398,7 @@ export function heartbeatService( retryReason: "missing_issue_comment", missingIssueCommentForRunId: run.id, }, - "status_only", + "normal_model", ); const responsibleUserId = await resolveResponsibleUserIdForRunContext( run, @@ -13435,7 +13437,7 @@ export function heartbeatService( retryOfRunId: run.id, retryReason: "missing_issue_comment", }, - "status_only", + "normal_model", ), status: "queued", requestedByActorType: "system", From 236d43bb5a6c9eb5fb9f32ca6a9e8dba0a41e48e Mon Sep 17 00:00:00 2001 From: Lourenco Castro Date: Fri, 11 Sep 2026 14:24:17 +0200 Subject: [PATCH 2/2] style(test): align retry assertions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../heartbeat-comment-wake-batching.test.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/__tests__/heartbeat-comment-wake-batching.test.ts b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts index 0a06dd7883..81b8baf459 100644 --- a/server/src/__tests__/heartbeat-comment-wake-batching.test.ts +++ b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts @@ -3425,19 +3425,19 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { ), ); expect(missingCommentRetries).toHaveLength(1); - expect(missingCommentRetries[0]?.payload).toMatchObject({ - retryReason: "missing_issue_comment", - }); - for (const key of [ - "modelProfile", - "paperclipModelProfile", - "recoveryIntent", - "allowDeliverableWork", - "allowDocumentUpdates", - "resumeRequiresNormalModel", - ]) { - expect(missingCommentRetries[0]?.payload).not.toHaveProperty(key); - } + expect(missingCommentRetries[0]?.payload).toMatchObject({ + retryReason: "missing_issue_comment", + }); + for (const key of [ + "modelProfile", + "paperclipModelProfile", + "recoveryIntent", + "allowDeliverableWork", + "allowDocumentUpdates", + "resumeRequiresNormalModel", + ]) { + expect(missingCommentRetries[0]?.payload).not.toHaveProperty(key); + } } finally { gateway.releaseFirstWait(); await gateway.close();