pr-greenlight
Purpose
A single Agent Loop that takes uncommitted or committed work on a feature branch all the way to a green PR. It replaces the manual chain of:
- (forgotten) local checks → push → CI fails → new session to fix → repeat
$create-pr→ wait →$babysit-pr→ wait → fix → push → wait
The loop runs: Pre-flight → Auto-fix → Push/PR → Monitor CI → Fix CI → Loop until green.
When to trigger
$pr-greenlight- full loop: preflight + create PR + monitor$pr-greenlight preflight- only run local pre-flight checks, no push- User says "帮我把这个 PR 搞绿", "run preflight", "pre-flight checks", "搞定 PR"
State file
/tmp/codex-pr-greenlight-state.json:
{
"phase": "preflight|pushed|monitoring",
"pr_number": null,
"round": 0,
"max_rounds": 4,
"preflight_passed": false,
"checks_run": [],
"fixes_applied": [],
"processed_comment_ids": [],
"started_at": "ISO timestamp"
}
Lock file: /tmp/codex-pr-greenlight.lock
Phase 1 — Perceive: Analyze what changed
touch /tmp/codex-pr-greenlight.lock
Run in parallel:
git rev-parse --abbrev-ref HEAD
git status --short
git diff main...HEAD --stat
git diff main...HEAD --name-only
git log main..HEAD --format="%h %s"
From the file list, classify what changed into categories:
| Category | Detection pattern |
|---|---|
rust |
*.rs, Cargo.toml, Cargo.lock |
frontend |
src/**/*.{ts,tsx,js,jsx,css}, package.json, bun.lockb |
api-endpoints |
*.rs files containing #[utoipa::path] or #[tauri::command] changes |
tauri-ipc |
changes in src-tauri/src/, DTO structs used by Tauri commands |
openapi |
schema/openapi.json, files with #[utoipa::path] |
docs-site |
docs-site/** |
markdown |
*.md (outside docs-site) |
generated |
src/api/generated/**, src/lib/ipc-bindings.generated.ts |
Store the categories in state as change_categories.
Refuse conditions (same as create-pr):
- On
main→ refuse git log main..HEADempty → refuse (nothing to PR)gh auth statusfails → ask user to auth
Phase 2 — Reason & Act: Pre-flight checks
Run ONLY the checks relevant to change_categories. Each check follows: run → detect failure → auto-fix → re-run → confirm.
2a — Format (always run)
# Rust format (if rust changed)
cargo fmt --all --check 2>&1
# If fails:
cargo fmt --all
git add -u '*.rs'
# Frontend format (if frontend/markdown/docs changed)
bun run format 2>&1
# Prettier auto-fixes; stage changed files
git add -u
2b — Code generation drift (the #1 CI failure cause)
Only if rust or api-endpoints changed:
# 1. OpenAPI schema
bun run gen:openapi 2>&1
git diff --exit-code schema/openapi.json
# If diff: stage it
git add schema/openapi.json
# 2. IPC bindings (if tauri-ipc changed)
cargo test -p uc-tauri --test specta_export 2>&1
git diff --exit-code src/lib/ipc-bindings.generated.ts
# If diff: stage it
git add src/lib/ipc-bindings.generated.ts
# 3. API client (if openapi.json changed in step 1 or was already changed)
bun run gen:client 2>&1
git diff --exit-code src/api/generated/
# If diff: stage it
git add src/api/generated/
2c — Lint (scoped to changed files)
# Frontend lint — scope to changed files only (full repo has baseline debt)
CHANGED_TS=$(git diff main...HEAD --name-only --diff-filter=d -- '*.ts' '*.tsx' '*.js' '*.jsx' | head -50)
if [ -n "$CHANGED_TS" ]; then
bun run lint --fix -- $CHANGED_TS 2>&1
git add -u
fi
2d — Compilation & type check
# Rust check (if rust changed)
cargo check --workspace --locked 2>&1 | tail -40
# Frontend build = tsc + vite (if frontend changed)
bun run build 2>&1 | tail -40
If either fails, do NOT auto-fix — these are real errors. Report to user with the error output and ask how to proceed.
2e — Tests (if relevant code changed)
# Frontend tests (if frontend changed)
bun run test -- --run 2>&1 | tail -40
# Rust tests are slow — only run focused tests for changed crates
# Detect changed crates from file paths
CHANGED_CRATES=$(git diff main...HEAD --name-only -- 'crates/*/src' 'apps/*/src' | sed 's|.*/\(crates/[^/]*\)/.*|\1|;s|.*/\(apps/[^/]*\)/.*|\1|' | sort -u)
for crate_path in $CHANGED_CRATES; do
crate_name=$(basename $crate_path)
cargo test -p $crate_name --lib 2>&1 | tail -20
done
2f — Markdown lint-staged simulation (if markdown changed)
# Only for non-docs-site markdown
CHANGED_MD=$(git diff main...HEAD --name-only --diff-filter=d -- '*.md' ':!docs-site/**' | head -20)
if [ -n "$CHANGED_MD" ]; then
for f in $CHANGED_MD; do
node scripts/fix-md-cjk-emphasis.mjs "$f" 2>/dev/null
npx autocorrect --fix "$f" 2>/dev/null
npx prettier --write "$f" 2>/dev/null
done
git add -u '*.md'
fi
2g — Commit pre-flight fixes
If any files were staged by auto-fix steps:
git diff --cached --stat
If there are staged changes, commit them:
git commit -m "$(cat <<'EOF'
chore: pre-flight auto-fix (format, codegen, lint)
EOF
)"
Update state: preflight_passed = true.
Pre-flight summary
Print a table:
Pre-flight results:
✓ cargo fmt
✓ prettier
✓ OpenAPI schema (regenerated, +12 -3)
✓ IPC bindings (no drift)
✓ API client (regenerated)
✓ ESLint (2 auto-fixed)
✓ cargo check
✓ tsc + vite build
✓ vitest (14 passed)
✓ cargo test uc-core (23 passed)
— cargo test uc-daemon (skipped, no changes)
If the user only asked for $pr-greenlight preflight, stop here. Clean up the lock file and report results.
Phase 3 — Push & Create PR
If pre-flight passed, proceed to push and create PR.
Delegate to the create-pr skill logic:
- Branch name review (propose rename if needed)
- docs-site impact scan
- Push and
gh pr create
Key points:
- Follow all
create-prconventions (title format, body format, branch name check) - Record the PR number in state
After PR is created, immediately proceed to Phase 4.
Phase 4 — Monitor CI (Agent Loop)
This is the core monitoring loop, similar to babysit-pr but integrated.
4a — Wait for CI to start
# Give CI 30s to register checks
sleep 30
gh pr checks --json name,state,conclusion 2>/dev/null
If no checks have registered, keep the current turn active, wait with the available command/session wait mechanism, and check again. Do not end the task merely because CI has not appeared yet.
4b — Check status
gh pr checks --json name,state,conclusion,link 2>/dev/null
Classify:
- All passed → go to 4c (check for review comments before declaring victory)
- Some pending -> continue monitoring in the same turn
- Some failed → proceed to 4d
4c — Check for bot review comments
Same as babysit-pr Step 2b — check all three GitHub comment endpoints:
BOT_FILTER='select(.user.login | test("coderabbitai|github-actions|codecov"))'
# Inline review comments
gh api repos/UniClipboard/UniClipboard/pulls/<PR>/comments \
--jq "[.[] | ${BOT_FILTER} | {id, user: .user.login, body, path, line}]"
# Review bodies
gh api repos/UniClipboard/UniClipboard/pulls/<PR>/reviews \
--jq "[.[] | ${BOT_FILTER} | {id, user: .user.login, body, state}]"
# Issue comments (CodeRabbit walkthrough lives here)
gh api repos/UniClipboard/UniClipboard/issues/<PR>/comments \
--jq "[.[] | ${BOT_FILTER} | {id, user: .user.login, body: (.body | .[0:2000])}]"
Filter out processed_comment_ids. If zero unprocessed actionable comments AND all checks pass → Success. Clean up.
4d — Fix failures
For each failed check or new review comment:
- Diagnose: Fetch logs (
gh run view <run_id> --log-failed | tail -100), read relevant code - Classify:
- Format/lint failure → auto-fix (same as pre-flight 2a-2c)
- Code generation drift → re-run gen commands (same as 2b)
- Compilation error → fix the code
- Test failure → fix the code
- CodeRabbit comment → evaluate: must-fix (correctness/security) vs nit (skip)
- Fix: Edit files surgically
- Validate locally: Run the same check that failed to confirm fix works
- Commit and push:
git add <specific files>
git commit -m "$(cat <<'EOF'
fix: address CI feedback (pr-greenlight round N)
EOF
)"
git push
Increment round in state. If round >= max_rounds → clean up with warning.
4e - Loop back
After pushing a fix, prefer gh pr checks --watch --interval 30 and poll its command session in bounded intervals. Re-enter at Phase 4b when the checks finish. Keep the current Codex turn alive and continue reporting useful status while waiting.
Cleanup
On success:
rm -f /tmp/codex-pr-greenlight.lock /tmp/codex-pr-greenlight-state.json
✅ PR #<N> is green. All CI checks passed after <round> round(s).
URL: <pr_url>
Pre-flight caught: <list of issues auto-fixed before push>
CI rounds: <N>
On max rounds (4):
rm -f /tmp/codex-pr-greenlight.lock /tmp/codex-pr-greenlight-state.json
⚠️ PR #<N> still has failures after 4 rounds. Manual review needed.
Remaining failures: <list>
URL: <pr_url>
On timeout (90 min):
rm -f /tmp/codex-pr-greenlight.lock /tmp/codex-pr-greenlight-state.json
Safety guardrails
- Max 4 rounds of CI fix cycles (round only increments on push)
- 90-minute hard timeout
- Lock file prevents re-entrant triggering
- Never
--forcepush - Never amend existing commits
- If a fix breaks something else (cargo check / tsc fails after edit), revert the fix and report
- Skip CodeRabbit style nits — only fix correctness/security/performance issues
- Pre-flight auto-fixes go in a dedicated commit, not mixed with feature work
- Scoped lint: only lint changed files (repo has baseline ESLint debt)
processed_comment_idsprevents duplicate processing
Interaction with existing skills
- Supersedes manual
$create-pr+$babysit-prchain for the common case $create-prand$babysit-prremain available for standalone use- If a PR already exists for the branch, skip Phase 3 (push only, no
gh pr create) - If the user invokes
$pr-greenlight preflight, only Phase 1-2 execute (no push, no PR)
Anti-patterns
- Running all checks regardless of what changed (wastes time on unrelated failures)
- Auto-fixing compilation errors without understanding them
- Pushing broken code because "CI will catch it"
- Running
bun run linton entire repo (33 baseline errors will confuse the loop) - Treating
cargo fmtfailures as real bugs (they're always auto-fixable) - Skipping the local validation step after fixing a CI failure (leads to ping-pong)
- Processing old/already-addressed CodeRabbit comments
- Applying every CodeRabbit nit blindly