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:
parent
740335a149
commit
2bd97791c9
@ -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(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user