OmniNode-ai

fix-prs

Org-wide PR repair — scans all repos for broken PRs and autonomously fixes merge conflicts, failing CI, and unaddressed review comments

OmniNode-ai 2 3 Updated 3mo ago

Resources

1
GitHub

Install

npx skillscat add omninode-ai/omniclaude/fix-prs

Install via the SkillsCat registry.

SKILL.md

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:

  1. Merge conflicts — rebase onto pr.baseRefName (NEVER hardcoded main); must resolve before CI can run reliably
  2. Failing CI — invoke ci-fix-pipeline sub-skill; skip checks requiring external secrets/infra
  3. 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 ModelSkillResult

CI 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 check

If 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 resolved
  • partial — some fixed, some remain (partial|failed|needs_human|blocked_external)
  • nothing_to_fix — all PRs are merge-ready or unknown-state
  • error — 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 PR
  • pr-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-sweep skill — merges PRs that are already fix-prs clean
  • pr-queue-pipeline skill — orchestrates fix-prs → merge-sweep in sequence
  • ci-fix-pipeline skill — per-PR CI failure analysis and repair
  • pr-review-dev skill — address code review comments
  • OMN-2636 — integration test suite