feat(fleet): resilient PR merge on ship (inline attempt + background retry)

The corporate proxy intermittently 407s GitHub's API, so a single gh pr merge can
fail transiently. Try once inline (fast path), then retry in the background with
backoff (3s/8s/20s/45s) without blocking the ship; mark prState=merged when one
lands. Best-effort throughout.
This commit is contained in:
saravanakumardb1 2026-05-31 16:13:52 -07:00
parent 740335a149
commit 2bd97791c9

View File

@ -643,17 +643,44 @@ const execFileAsync = promisify(execFile);
* authenticated (the coordinator host). Best-effort: a failure leaves the PR open * authenticated (the coordinator host). Best-effort: a failure leaves the PR open
* and never fails the ship. Marks the run prState=merged on success. * and never fails the ship. Marks the run prState=merged on success.
*/ */
async function ghMergePr(prUrl: string): Promise<boolean> {
try {
await execFileAsync('gh', ['pr', 'merge', prUrl, '--squash', '--delete-branch'], {
timeout: 60_000,
});
return true;
} catch {
// Network/proxy hiccup (e.g. intermittent 407), checks pending, or conflict.
return false;
}
}
async function mergeRunPrOnShip(jobId: string, latest: FleetRunDoc | undefined): Promise<void> { async function mergeRunPrOnShip(jobId: string, latest: FleetRunDoc | undefined): Promise<void> {
if (process.env.FLEET_SHIP_MERGES_PR !== '1') return; if (process.env.FLEET_SHIP_MERGES_PR !== '1') return;
if (!latest?.prUrl || latest.prState === 'merged') return; if (!latest?.prUrl || latest.prState === 'merged') return;
try { const { prUrl, id: runId } = latest;
await execFileAsync('gh', ['pr', 'merge', latest.prUrl, '--squash', '--delete-branch'], { // Fast attempt inline (so a healthy proxy merges immediately without delaying ship).
timeout: 60_000, if (await ghMergePr(prUrl)) {
}); await repo.updateRun(runId, jobId, { prState: 'merged' });
await repo.updateRun(latest.id, jobId, { prState: 'merged' }); return;
} catch {
// best-effort — leave the PR open if the merge is blocked (checks, conflicts…)
} }
// The corporate proxy intermittently 407s GitHub's API. Retry in the BACKGROUND
// with backoff so the ship response is never blocked; mark merged when one lands.
void (async () => {
for (const delayMs of [3_000, 8_000, 20_000, 45_000]) {
await new Promise(r => setTimeout(r, delayMs));
if (await ghMergePr(prUrl)) {
try {
await repo.updateRun(runId, jobId, { prState: 'merged' });
} catch {
/* run gone — ignore */
}
return;
}
}
})().catch(() => {
/* detached best-effort — never throws */
});
} }
export async function patchJobFenced( export async function patchJobFenced(