Files
pr-agent-server/scripts/README.md
T
asepharyana c48adea6c3 feat(worker): upstream fork auto-sync — pull+merge fork repos hourly
Merge new upstream (parent) commits into every fork in the App installation,
gated by a per-repo interval (default 1h), inside the existing 5-minute tick
(STEP 0, max 2 forks/tick, oldest-first).

- Conflicted merges are resolved by Claude Code (merge-reconciler rules:
  never wholesale --ours/--theirs, verify with the repo's own
  typecheck+tests, commit --no-edit; Claude never pushes — harness does).
- Clean merges get a single Claude Code quality pass commit.
- Push path: owner PAT (gh CLI) first — the App lacks workflows:write and a
  workflows-touching merge is rejected for the App token; App token fallback.
- Protected default branch: detected from the push result (GH006 /
  required-status-check) → upstream-sync-<ts> branch + PR through the normal
  pipeline; duplicate open sync PRs are skipped.
- CI safety: after a direct push, ticks verify the fork CI at our merge sha;
  red CI at OUR merge (still the tip) → sha-guarded force-revert to
  pre-merge sha + Discord notify; never reverts foreign commits.
- Discord: synced / PR opened / reverted / skipped-once on pr-agent-ops.
- merge_pr gains the same PAT fallback (a PR merge touching workflows is a
  workflow-file push).
- CLI: --sync-status, --sync-only <repo> [--dry].
- Tests: scripts/test_pr_queue_sync.py (46 assertions, monkeypatched, no
  network); py_compile clean.
- Plan: .hermes/plans/2026-09-21-upstream-auto-sync.md
2026-09-21 17:04:39 +07:00

3.6 KiB

PR Queue Worker

pr-queue-worker.py — the orchestration cron that watches open PRs across all repos where the PR-Agent GitHub App is installed and drives the full lifecycle:

  1. Open PR found → ensure a PR-Agent review exists (fabricates a webhook to pr-agent.asepharyana.my.id if not)
  2. Toolchain pin guard → closes dependabot PRs that bump pinned toolchain majors (see TOOLCHAIN_PINS map — typescript/eslint/@tsparticles/…) instead of waiting on them forever
  3. AI auto-fix → runs Claude Code (-p) on the PR head for up to AI_FIX_MAX_TURNS turns, then pushes the fix
  4. Safety analysis → parses the review body for security/major-issue blockers; score must be ≥ 6/10
  5. CI gate → waits for the required check to pass (closes stale dependabot PRs stuck failing CI for > STALE_CI_CLOSE_DAYS)
  6. Approve + merge

Upstream Fork Auto-Sync

Since 2026-09-21 the same 5-minute tick also syncs every repo in the App installation whose GitHub metadata says fork: true: new upstream (parent) commits are merged (never rebased) into the fork's default branch, gated by a per-repo interval (default 1 hour; UPSTREAM_SYNC config block).

  • Conflicted merge → Claude Code resolves it (merge-reconciler rules: merge hunks by hand, never wholesale --ours/--theirs, run the repo's own typecheck/tests before committing). Claude never pushes — the harness does.
  • Clean merge → one Claude Code quality pass over the merged files, committed as fix: auto-fix code quality [skip ci].
  • Protected default branch → detect from the push result (GH006 / required-status-check) and fall back to opening an upstream-sync-* PR that the normal pipeline (review → AI fix → CI → approve → merge) finishes.
  • CI safety → after a direct push the worker verifies the fork's CI at our merge commit; a red CI at OUR merge sha (still the tip, no human commits on top) force-reverts to the pre-merge sha. Watch stops after 6 h.
  • Push credentials → owner PAT (gh CLI) first because the App lacks workflows:write; App token is the fallback. Clones use the App token.
  • State → /tmp/pr-queue-sync-state.json (skip/interval/pending-verify), workdirs /tmp/pr-queue-sync-work/.
  • Notifications → same pr-agent-ops Discord webhook: synced, PR opened, reverted, skipped-once.

Manual/dev:

python3 scripts/pr-queue-worker.py --sync-status
python3 scripts/pr-queue-worker.py --sync-only asepharyana/shiro-neko --dry   # stops before push
python3 scripts/pr-queue-worker.py --sync-only asepharyana/shiro-neko

Tests: python3 scripts/test_pr_queue_sync.py (46 assertions; no network — gh_api/git/Claude/push are monkeypatched).

Deployment

  • Cron: Hermes cron job 04f13b9fdadc, every 5 minutes
  • Live path: ~/.hermes/scripts/pr-queue-worker.py (cron reads this file — keep it in sync with this repo copy)
  • Secrets: NOT in this file. Hydrated at import from ~/.hermes/.env ( PR_AGENT_APP_ID, PR_AGENT_KEY_PATH, PR_AGENT_WEBHOOK_SECRET, PR_AGENT_WEBHOOK_URL). The GitHub App private key lives at ~/.hermes/keys/pr-agent-key.pem (gitignored, never commit).

Sync note

This repo is the canonical version. The live cron copy under ~/.hermes/scripts/ is what actually runs — after editing here, cp scripts/pr-queue-worker.py ~/.hermes/scripts/pr-queue-worker.py and verify with:

/home/code/hermes-agent/.venv/bin/python3 -m py_compile ~/.hermes/scripts/pr-queue-worker.py
timeout 90 /home/code/hermes-agent/.venv/bin/python3 ~/.hermes/scripts/pr-queue-worker.py