Org-wide PR repair — scans all repos for broken PRs and autonomously fixes merge conflicts, failing CI, and unaddressed review comments
Resources
1Install
npx skillscat add omninode-ai/omniclaude/fix-prs Install via the SkillsCat registry.
Fix PRs
Overview
Fully autonomous skill that scans all open PRs across omni_home repos and repairs each one.
Three failure categories are processed in priority order per PR: merge conflicts, failing CI,
unaddressed review comments. No Slack gate — runs to completion autonomously.
Supersedes: OMN-2400 (ci-fix-pipeline Node) — this skill is a superset.
Announce at start: "I'm using the fix-prs skill."
Failure Category Priority (per PR)
Processed in this order. A PR may have multiple issues; all applicable categories run:
- Merge conflicts — rebase onto
pr.baseRefName(NEVER hardcodedmain); must resolve before CI can run reliably - Failing CI — invoke
ci-fix-pipelinesub-skill; skip checks requiring external secrets/infra - Review comments — invoke
pr-review-dev; never auto-dismiss reviews, never force-push unless--allow-force-push
PR Classification Predicates
def needs_conflict_work(pr) -> bool:
return pr["mergeable"] == "CONFLICTING"
def needs_ci_work(pr) -> bool:
return not is_green(pr) # any REQUIRED check not SUCCESS
def needs_review_work(pr) -> bool:
return pr.get("reviewDecision") == "CHANGES_REQUESTED"
def is_green(pr) -> bool:
required = [c for c in pr["statusCheckRollup"] if c.get("isRequired", False)]
if not required:
return True
return all(c.get("conclusion") == "SUCCESS" for c in required)
def is_merge_ready(pr) -> bool:
return (
pr["mergeable"] == "MERGEABLE"
and is_green(pr)
and pr.get("reviewDecision") in ("APPROVED", None)
)
def pr_state_unknown(pr) -> bool:
return pr["mergeable"] == "UNKNOWN"Arguments
| Argument | Default | Description |
|---|---|---|
--repos |
all | Comma-separated repo names to scan |
--category |
all |
conflicts | ci | reviews | all |
--max-total-prs |
20 | Hard cap on PRs processed per run |
--max-parallel-prs |
5 | Concurrent fix agents |
--max-parallel-repos |
3 | Repos scanned in parallel |
--max-fix-iterations |
3 | Max ci-fix-pipeline iterations per PR |
--authors |
all | Limit to PRs by these GitHub usernames (comma-separated) |
--allow-force-push |
false | Permit force-push after rebase; always leaves PR comment |
--ignore-ledger |
false | Bypass idempotency ledger |
--run-id |
generated | Pipeline run ID for claim registry ownership + ledger namespacing |
--dry-run |
false | Zero filesystem writes — no claim files, no ledger updates, no PR mutations |
Idempotency Ledger
Per-run record at ~/.claude/pr-queue/<date>/run_<run_id>.json:
{
"OmniNode-ai/omniclaude#247": {
"head_sha": "cbca770e",
"last_error_fingerprint": "SHA256(phase|error_class|check_name|first_error_line)",
"last_result": "fixed",
"retry_count": 1
}
}Retry policy: retry a PR only if head_sha changed OR last_error_fingerprint differs.
If retry_count >= 3 and neither condition is met: skip with result: needs_human.
--ignore-ledger: bypass all ledger checks and retry every PR.
Execution Algorithm
1. SCAN (parallel, up to --max-parallel-repos):
gh pr list per repo, classify each PR:
- is_merge_ready() → skip (merge-sweep handles these)
- pr_state_unknown() → skip with warning
- needs_conflict_work() OR needs_ci_work() OR needs_review_work() → add to work_queue[]
Apply --authors filter
Apply --category filter (skip categories not in --category)
Check ledger: skip PRs where retry_count >= 3 AND head_sha + fingerprint unchanged
Apply --max-total-prs cap to work_queue[]
2. If work_queue is empty: emit ModelSkillResult(status=nothing_to_fix), exit
3. DISPATCH parallel agents (up to --max-parallel-prs), one per PR:
For each PR:
a. IF needs_conflict_work AND --category includes conflicts:
git fetch origin <pr.baseRefName>
git rebase origin/<pr.baseRefName> ← pr.baseRefName, NOT "main"
Resolve conflicts in each file; git rebase --continue
git push --force-with-lease (or git push if no rebase happened)
Post PR comment: "Resolved merge conflict via rebase onto <baseRefName>"
On rebase failure: record result=failed, reason=conflict_unresolved, skip to next PR
b. IF conflict was resolved in step a: RE-QUERY GitHub state
Wait up to 30s for GitHub to compute mergeable state after push
Re-classify PR with fresh state from gh pr view
If is_merge_ready() now: record result=fixed, reason=resolved_by_rebase, skip to c/d
c. IF needs_ci_work AND --category includes ci:
Query failing checks:
For each REQUIRED failed check:
If check name contains external secret indicators (AWS_, DEPLOY_, PROD_, service-account):
→ record result=blocked_external, blocked_check=<name>, skip ci-fix-pipeline for this check
If all failing checks are external: record result=blocked_external for PR, skip to d
Invoke: Skill(skill="onex:ci-fix-pipeline") with --max-fix-iterations <max_fix_iterations>
Collect result; record in ledger with new error_fingerprint
d. IF needs_review_work AND CI is now green AND --category includes reviews:
Invoke: Skill(skill="onex:pr-review-dev")
Guardrails:
- No auto-dismiss of reviews (never use gh api to dismiss)
- No force-push unless --allow-force-push is set
- Always post PR comment summarizing changes made
Collect result
4. COLLECT results from all agents
5. UPDATE ledger (head_sha, last_error_fingerprint, retry_count++)
6. EMIT ModelSkillResultCI Secrets Guard
Before invoking ci-fix-pipeline for any failing check, inspect the check name:
External infra indicators (skip ci-fix-pipeline for these checks):
- Check name contains: deploy, production, prod, staging, aws, gcp, azure,
service-account, docker-push, publish, release, upload-to
If the failing check matches any indicator:
→ record blocked_check = <check_name>
→ result = blocked_external for that check
→ do NOT invoke ci-fix-pipeline
→ continue to next checkIf ALL failing checks are blocked_external: record PR as result: blocked_external, skip ci-fix-pipeline entirely.
Force Push Guardrail
--allow-force-push = false (default):
After rebase: use git push --force-with-lease
If push requires force (not just lease): post PR comment explaining why, then push
--allow-force-push = true:
After rebase: use git push --force-with-lease
Always post PR comment: "Force-pushed after rebase onto <baseRefName> to resolve conflicts"ModelSkillResult
Written to ~/.claude/pr-queue/<date>/fix-prs_<run_id>.json:
{
"skill": "fix-prs",
"status": "all_fixed | partial | nothing_to_fix | error",
"run_id": "<run_id>",
"prs_scanned": 15,
"prs_processed": 8,
"prs_fixed": 5,
"prs_partial": 2,
"prs_failed": 1,
"prs_needs_human": 1,
"prs_blocked_external": 2,
"prs_skipped_ledger": 1,
"details": [
{
"repo": "OmniNode-ai/omniclaude",
"pr": 123,
"head_sha": "abc123",
"result": "fixed | partial | failed | needs_human | blocked_external | skipped",
"phases": {
"conflicts": "resolved | skipped | failed | not_needed",
"ci": "fixed | blocked_external | failed | skipped | not_needed",
"reviews": "addressed | skipped | failed | not_needed"
},
"blocked_check": null,
"skip_reason": null
}
]
}Status values:
all_fixed— every work_queue PR resolvedpartial— some fixed, some remain (partial|failed|needs_human|blocked_external)nothing_to_fix— all PRs are merge-ready or unknown-stateerror— scan failed or unrecoverable error
Failure Handling
| Error | Behavior |
|---|---|
| Rebase fails with unresolvable conflicts | Record result: failed, reason: conflict_unresolved |
| CI check requires external secrets | Record blocked_external, skip ci-fix-pipeline for that check |
ci-fix-pipeline skill fails |
Record result: partial or failed, continue other PRs |
pr-review-dev fails |
Record result: partial, continue other PRs |
gh pr list fails for a repo |
Log warning, skip that repo |
| PR retry_count >= 3, no progress | Record result: needs_human |
Sub-skills Used
ci-fix-pipeline(existing) — diagnose and fix CI failures per PRpr-review-dev(existing) — address GitHub review comments
Integration Test
Integration tests are in tests/integration/skills/fix_prs/test_fix_prs_integration.py (OMN-2636).
All tests are static analysis / structural tests — no live GitHub access or external credentials required.
Run with: uv run pytest tests/integration/skills/fix_prs/ -m unit -v
Test Coverage
| Test Case | Description | Marker |
|---|---|---|
| Dry-run contract | nothing_to_fix exit documented; idempotency ledger documented |
unit |
| Claim lifecycle | acquire_claim / release_claim / heartbeat in _lib/pr-safety/helpers.md |
unit |
| Claim expiry | Stale claim detection documented in helpers | unit |
| ClaimNotHeldError | Error class documented for mutation guard | unit |
| mutate_pr claim check | mutate_pr() asserts claim held before mutation |
unit |
| Boundary validation | boundary_validate() + BoundaryViolationError in helpers |
unit |
| Import boundary coverage | repo_class parameter enforces app/ui/infra separation |
unit |
| CI secrets guard | External infra check skip documented (boundary enforcement) | unit |
| Inventory/ledger consumption | Per-run ledger structure + retry policy documented | unit |
| Ledger check before dispatch | prompt.md references ledger before fix dispatch | unit |
| Max-total-prs cap | Blast radius cap documented | unit |
| TERMINAL_STOP_REASONS defined | All 14 canonical reasons present in helpers | unit |
| ledger_set_stop_reason | Function documented and enforces terminal reason set | unit |
| No direct gh pr merge | fix-prs repairs PRs, does not merge them | unit |
| No direct gh pr checkout | prompt.md delegates checkout to sub-skills | unit |
| No direct worktree creation | Use get_worktree() from _lib/pr-safety | unit |
| Delegates to ci-fix-pipeline | CI fixing dispatched to ci-fix-pipeline sub-skill | unit |
| Delegates to pr-review-dev | Review fixing dispatched to pr-review-dev sub-skill | unit |
| ModelSkillResult statuses | all_fixed / partial / nothing_to_fix / error documented | unit |
| Force push guardrail | --allow-force-push + PR comment requirement documented | unit |
| force-with-lease usage | prompt.md uses --force-with-lease, not bare --force | unit |
| baseRefName for rebase | Dynamic base ref (not hardcoded 'main') documented | unit |
See Also
merge-sweepskill — merges PRs that are already fix-prs cleanpr-queue-pipelineskill — orchestrates fix-prs → merge-sweep in sequenceci-fix-pipelineskill — per-PR CI failure analysis and repairpr-review-devskill — address code review comments- OMN-2636 — integration test suite