Skip to content

Commit 1be0f9c

Browse files
committed
Keep unsettled rebase failures current
1 parent 7780422 commit 1be0f9c

2 files changed

Lines changed: 17 additions & 10 deletions

File tree

‎runner/pre-merge.ts‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,17 @@ export class PreMergeCoordinator {
241241
} });
242242
return current;
243243
};
244+
// Rebase lifecycle writes advance task state without changing the reviewed pair. Bind a resulting failure to that
245+
// settled durable state without rebuilding history; if a read fails, the older binding remains safely stale.
246+
const bindCurrentFailure = () => {
247+
try {
248+
const snapshot = this.service.store.getSnapshot(identity);
249+
onObserved({ pair: { base: snapshot.base, head: snapshot.head }, binding: {
250+
stateVersion: this.service.store.getTask(identity).stateVersion,
251+
reviewVersion: this.service.store.reviewVersion(identity), snapshotId: snapshot.id,
252+
} });
253+
} catch { /* An unbound failure is conservatively historical. */ }
254+
};
244255
const load = () => track(this.service.load({ maxDurationMs: Math.min(30_000, reviewBudget()) }));
245256
let view = load(), task = this.service.store.getTask(identity);
246257
if (task.stateVersion !== expected.stateVersion || view.expected.reviewVersion !== expected.reviewVersion
@@ -292,18 +303,13 @@ export class PreMergeCoordinator {
292303
this.service.store.finishRebase(identity, reviewed, task.stateVersion, marker.attemptId,
293304
result.base, result.head, result.mappings);
294305
} catch (error) {
295-
if (error instanceof RebaseResourcesUnsettled) throw error;
306+
if (error instanceof RebaseResourcesUnsettled) {
307+
bindCurrentFailure();
308+
throw error;
309+
}
296310
try { await this.#cleanupRebase(identity, marker, rebaseBudget(true)); }
297311
catch (cleanup) { throw new AggregateError([error, cleanup], error instanceof Error ? error.message : 'Rebase failed.', { cause: error }); }
298-
// beginRebase and successful cleanup both advance task state. Bind the actionable failure to that settled
299-
// state without rebuilding history; if a read itself fails, retaining the older binding fails closed as stale.
300-
try {
301-
const snapshot = this.service.store.getSnapshot(identity);
302-
onObserved({ pair: { base: snapshot.base, head: snapshot.head }, binding: {
303-
stateVersion: this.service.store.getTask(identity).stateVersion,
304-
reviewVersion: this.service.store.reviewVersion(identity), snapshotId: snapshot.id,
305-
} });
306-
} catch { /* An unbound failure is conservatively historical. */ }
312+
bindCurrentFailure();
307313
throw error;
308314
}
309315
view = load();

‎test/pre-merge.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ it('marks a failed preparation stale when its final remote inspection was invali
197197
it('leaves an unsettled conflict and its rebase marker for startup recovery', async () => {
198198
const fixture = await rebaseFixture('feature\n', undefined, { unsettledRebase: true });
199199
expect(fixture.result).toMatchObject({ state: 'failed', reason: 'Conflict resources remain owned.' });
200+
expect(fixture.coordinator.last).toMatchObject({ state: 'failed', stale: false });
200201
expect(fixture.rebaseAborts()).toBe(0);
201202
expect(fixture.service.store.getTask(fixture.service.config.identity).rebaseInProgress).not.toBeNull();
202203
await fixture.coordinator.close(); await fixture.runner.close();

0 commit comments

Comments
 (0)