merge-steward 0.26.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,7 @@
1
1
  import type { QueueEntry } from "./types.ts";
2
2
  import type { ReconcileContext } from "./reconciler-core.ts";
3
3
  export declare function mergeHead(ctx: ReconcileContext, entry: QueueEntry): Promise<void>;
4
+ export declare function deletePrBranchAfterGitHubMarksMerged(ctx: ReconcileContext, entry: QueueEntry, options?: {
5
+ attempts?: number;
6
+ delayMs?: number;
7
+ }): Promise<void>;
@@ -2,6 +2,11 @@ import { CLEAN_CI, CLEAN_SPEC, emit, isBudgetExhausted, ref } from "./reconciler
2
2
  import { cleanupSpec, evictEntry, invalidateDownstream } from "./reconciler-evict.js";
3
3
  import { verifyPostMergeStatus } from "./reconciler-post-merge.js";
4
4
  import { getEffectiveMainStatus } from "./reconciler-main-status.js";
5
+ const DEFAULT_PR_MERGED_POLL_ATTEMPTS = 6;
6
+ const DEFAULT_PR_MERGED_POLL_DELAY_MS = 2_000;
7
+ function delay(ms) {
8
+ return new Promise((resolve) => setTimeout(resolve, ms));
9
+ }
5
10
  export async function mergeHead(ctx, entry) {
6
11
  emit(ctx, entry, "merge_revalidating");
7
12
  const prStatus = await ctx.github.getStatus(entry.prNumber);
@@ -133,10 +138,36 @@ export async function mergeHead(ctx, entry) {
133
138
  postMergeCheckedAt: new Date().toISOString(),
134
139
  }, `spec pushed to main; ${verificationResult.postMergeSummary}`);
135
140
  await cleanupSpec(ctx, entry);
136
- try {
137
- await ctx.github.deleteBranch(entry.prNumber);
138
- }
139
- catch {
140
- /* cosmetic */
141
+ await deletePrBranchAfterGitHubMarksMerged(ctx, entry);
142
+ }
143
+ export async function deletePrBranchAfterGitHubMarksMerged(ctx, entry, options = {}) {
144
+ const attempts = options.attempts ?? DEFAULT_PR_MERGED_POLL_ATTEMPTS;
145
+ const delayMs = options.delayMs ?? DEFAULT_PR_MERGED_POLL_DELAY_MS;
146
+ for (let attempt = 0; attempt < attempts; attempt++) {
147
+ let merged = false;
148
+ try {
149
+ merged = (await ctx.github.getStatus(entry.prNumber)).merged;
150
+ }
151
+ catch {
152
+ // Keep polling briefly. If GitHub is unavailable, branch cleanup is
153
+ // cosmetic; preserving correct PR merge classification matters more.
154
+ }
155
+ if (merged) {
156
+ try {
157
+ await ctx.github.deleteBranch(entry.prNumber);
158
+ }
159
+ catch {
160
+ emit(ctx, entry, "pr_branch_cleanup_failed", {
161
+ detail: "GitHub marked the PR merged, but deleting the head branch failed",
162
+ });
163
+ }
164
+ return;
165
+ }
166
+ if (attempt < attempts - 1 && delayMs > 0) {
167
+ await delay(delayMs);
168
+ }
141
169
  }
170
+ emit(ctx, entry, "pr_branch_cleanup_deferred", {
171
+ detail: "waiting for GitHub to classify the fast-forwarded PR as merged before deleting the head branch",
172
+ });
142
173
  }
package/dist/types.d.ts CHANGED
@@ -224,7 +224,7 @@ export interface QueueConfig {
224
224
  flakyRetries: number;
225
225
  pollIntervalMs: number;
226
226
  }
227
- export type ReconcileAction = "promoted" | "fetch_started" | "main_broken" | "branch_mismatch" | "spec_build_started" | "spec_build_succeeded" | "spec_build_conflict" | "ci_triggered" | "ci_pending" | "ci_passed" | "ci_failed" | "ci_flaky_retry" | "merge_revalidating" | "merge_succeeded" | "merge_rejected" | "merge_external" | "policy_changed" | "evicted" | "invalidated" | "retry_gated" | "budget_exhausted" | "merge_waiting_approval" | "merge_waiting_main" | "main_pending_bypassed" | "sanitized_closed" | "sanitized_duplicate" | "post_merge_verification_started" | "post_merge_verification_completed" | "branch_unreachable";
227
+ export type ReconcileAction = "promoted" | "fetch_started" | "main_broken" | "branch_mismatch" | "spec_build_started" | "spec_build_succeeded" | "spec_build_conflict" | "ci_triggered" | "ci_pending" | "ci_passed" | "ci_failed" | "ci_flaky_retry" | "merge_revalidating" | "merge_succeeded" | "merge_rejected" | "merge_external" | "policy_changed" | "evicted" | "invalidated" | "retry_gated" | "budget_exhausted" | "merge_waiting_approval" | "merge_waiting_main" | "main_pending_bypassed" | "pr_branch_cleanup_deferred" | "pr_branch_cleanup_failed" | "sanitized_closed" | "sanitized_duplicate" | "post_merge_verification_started" | "post_merge_verification_completed" | "branch_unreachable";
228
228
  export interface ReconcileEvent {
229
229
  at: string;
230
230
  entryId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "merge-steward",
3
- "version": "0.26.0",
3
+ "version": "0.26.1",
4
4
  "description": "Serial merge queue for GitHub — rebase, CI-gate, and merge PRs one at a time",
5
5
  "type": "module",
6
6
  "repository": {