@@ -20,7 +20,7 @@ import {
2020} from "../action.js" ;
2121import { readAppState } from "../application-state/script-helpers.js" ;
2222import { isReadOnlyShellCommand } from "../coding-tools/index.js" ;
23- import { getDbExec } from "../db/client.js" ;
23+ import { getDbExec , isTransientDatabaseError } from "../db/client.js" ;
2424import { extensionIdFromPathname } from "../extensions/path.js" ;
2525import { preUploadAttachments } from "../file-upload/pre-upload-attachments.js" ;
2626import { isMcpActionResult } from "../mcp-client/app-result.js" ;
@@ -1188,6 +1188,35 @@ export async function resolveAgentOwnerEmail(
11881188}
11891189
11901190const MAX_RETRIES = 3 ;
1191+ const COMPLETION_DATABASE_RETRY_DELAYS_MS = [ 250 , 750 , 1_500 ] as const ;
1192+
1193+ /**
1194+ * A transient database failure in the completion callback used to prevent a
1195+ * background continuation from ever being handed off. Keep the retry bounded
1196+ * and database-only so a permanent persistence error still fails loudly.
1197+ */
1198+ export async function runCompletionCallbackWithDatabaseRetry (
1199+ callback : ( ) => void | Promise < void > ,
1200+ options ?: { sleep ?: ( ms : number ) => Promise < void > } ,
1201+ ) : Promise < void > {
1202+ const sleep =
1203+ options ?. sleep ??
1204+ ( ( ms : number ) => new Promise < void > ( ( resolve ) => setTimeout ( resolve , ms ) ) ) ;
1205+
1206+ for ( let attempt = 0 ; ; attempt += 1 ) {
1207+ try {
1208+ await callback ( ) ;
1209+ return ;
1210+ } catch ( error ) {
1211+ const retryDelay = COMPLETION_DATABASE_RETRY_DELAYS_MS [ attempt ] ;
1212+ if ( ! isTransientDatabaseError ( error ) || retryDelay === undefined ) {
1213+ throw error ;
1214+ }
1215+ await sleep ( retryDelay ) ;
1216+ }
1217+ }
1218+ }
1219+
11911220/**
11921221 * Retry budget override for `builder_gateway_error` — the no-detail Builder
11931222 * gateway fallback. Production data shows this code is almost never
@@ -9181,7 +9210,9 @@ export function createProductionAgentHandler(
91819210 options . onRunComplete || trackedProgressRunId
91829211 ? async ( run : ActiveRun ) => {
91839212 try {
9184- await options . onRunComplete ?.( run , threadId ) ;
9213+ await runCompletionCallbackWithDatabaseRetry ( ( ) =>
9214+ options . onRunComplete ?.( run , threadId ) ,
9215+ ) ;
91859216 } catch ( err ) {
91869217 await completeTrackedProgressRun ( run , err ) ;
91879218 throw err ;
0 commit comments