Skip to content

orb(disposition): merge_blocked_sha never clears and mergeAttemptCount never resets — one token blip permanently strands green approved PRs, invisibly #9012

Description

@JSONbored

A green, approved PR can be permanently un-mergeable — silently

merge_blocked_sha is set by handleMergeFailure (src/services/agent-action-executor.ts ~971-1008) and suppresses all future merge planning while mergeBlockedSha === headSha (src/settings/agent-actions.ts ~1056, ~1064). No code path ever sets it back to null — it is head-scoped (src/db/repositories.ts ~4216-4223), so the only escape is the contributor pushing a new commit.

classifyMergeFailure (src/services/merge-failure.ts ~70-85) marks as terminal: 401, generic 403, 405, 409, and merge-conflict text. But several of those are global and transient:

  • 401 = App key rotation / token blip — fleet-wide, affects every in-flight merge at once.
  • 409 = "required status check absent" — can be a checks-still-registering race.
  • 403 rate-limit/abuse texts are "possibly transient" but convert to terminal after MERGE_RETRY_CAP = 5 sweeps, i.e. a sustained rate-limit window terminally blocks everything that passed through it.

Compounding bug: mergeAttemptCount is never reset. The bump at repositories.ts ~4139-4152 gates only the increment, and schema.ts ~378-383 explicitly omits it from the GitHub-sync SET clause — contradicting its own documentation ("a new commit's attempts start fresh"). So after one exhaustion, every subsequent head is one-strike-terminal on any transient failure.

Why it's silent: mergeBlockedReason is never rendered on any public or panel surface (readers are the planner, audit, and PostHog only). With review_state_label enabled the PR even keeps a ready-to-merge label. A contributor looking at a green, approved, ready-to-merge PR has no signal and no reason to push. The re-gate sweep cannot rescue it either: a PR regated once is permanently sweep-ineligible (src/settings/agent-sweep.ts ~166-174, #never-endless-reregate) and repair priority excludes clean-mergeable PRs (processors.ts ~1160-1167).

Net: one token rotation can strand every in-flight merge in the fleet, permanently, with no visible cause.

Fix

  1. Reset mergeAttemptCount when headSha changes — restores the documented invariant.
  2. Distinguish terminal-by-commit from terminal-by-infra. Reserve persistent merge_blocked for genuinely commit-specific causes (real conflict). For global-transient classes (401, 403 secondary-rate-limit) use a TTL re-probe: retry once installation health shows the token/permission recovered.
  3. Surface mergeBlockedReason on the panel, and drop the misleading ready-to-merge label while blocked.
  4. Add a maintainer "clear merge block" action.

Acceptance

  • Simulated 401 during merge → after token recovery, the PR merges autonomously with no new commit.
  • A genuine merge conflict still blocks terminally until the contributor rebases.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.orbGittensory Orb related - maintainer self-hosting analytics.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions