mit einem Klick
pr-watch-fix
// Watch the GitHub PR for the current branch, wait for CI to finish, and autonomously fix failing jobs by reading logs, editing sources, and pushing. Stops cleanly when stuck.
// Watch the GitHub PR for the current branch, wait for CI to finish, and autonomously fix failing jobs by reading logs, editing sources, and pushing. Stops cleanly when stuck.
Style rules for updating CHANGELOG.md entries. Use whenever the user asks to update, add to, or write entries in CHANGELOG.md.
Implement a feature, bug fix, or code change in sfdx-hardis. Use whenever the user asks to add a feature, fix a bug, implement something, or make a code change - with or without a prior /design phase.
Decision framework for fixing jscpd (copy-paste detector) errors. Use when asked to fix jscpd issues, copy-paste errors, clones or COPYPASTE lint failures.
sfdx-hardis project architecture, technology stack, provider pattern, configuration system, and project structure. Use when working with project structure, providers, hooks, or config.
How sfdx-hardis monitoring commands, notification types, frequency, and per-channel routing fit together. Use when adding a new monitoring command, adding a new notification type, changing default routing thresholds, or wiring a new channel.
Code examples and patterns for using i18n translations in sfdx-hardis source code (uxLog, uxLogTable, prompts, markers). Use when adding or modifying user-visible strings.
| name | pr-watch-fix |
| description | Watch the GitHub PR for the current branch, wait for CI to finish, and autonomously fix failing jobs by reading logs, editing sources, and pushing. Stops cleanly when stuck. |
| allowed-tools | Bash Read Grep Glob Edit Write AskUserQuestion |
| user-invocable | true |
Watch the open PR for the current branch, wait for CI, and fix failures.
Repeat until the PR is fully green or you stop intentionally:
Before doing anything else, cancel a previous run's still-running poller. Re-invoking /pr-watch-fix always wins -- the new run resets state.
Use TaskList to find Monitors whose description starts with PR watch: (the convention used by step 3) and call TaskStop on each. Do not stop tasks that don't start with this prefix -- they belong to other work.
BRANCH="$(git branch --show-current)"
PR_JSON="$(gh pr list --head "$BRANCH" --state open --json number,url,headRefOid --limit 1)"
PR_NUMBER="$(printf '%s' "$PR_JSON" | jq -r '.[0].number // empty')"
PR_NUMBER is empty -> STOP. Tell the user there is no open PR for the branch.headRefOid so you can detect when a new push lands.Always query BOTH gh pr checks and gh run list -- gh pr checks only sees workflow checks that have already registered with the PR, and there is a 30-90s lag between a workflow being triggered and its check appearing. A workflow that is queued or just-started in_progress may not yet be in gh pr checks, so a snapshot showing "all pass" can be a lie when other runs are still pending registration. This bit you before -- don't trust a single signal.
gh pr checks "$PR_NUMBER" --json name,bucket,state,workflow,link
gh run list --branch "$BRANCH" --limit 20 --json status,conclusion,name,event,createdAt,databaseId
sfdx-hardis CI surface (as of writing):
.github/workflows/test.yml): linux-unit-tests, windows-unit-tests, nuts (matrix over ubuntu/windows). Note: nuts does not get secrets on fork PRs and may legitimately be unable to run there..github/workflows/mega-linter.yml): runs linters across the repo. Auto-commits fixes back to the branch as megalinter-bot..github/workflows/build-deploy-docs.yml): builds the MkDocs site..github/workflows/docker-security-scan.yml): trivy/osv scans on the published image.Classify each check by bucket/state:
pass -> successfail, cancel -> failureskipping -> treat as success (e.g. nuts skipping on a fork PR because secrets are unavailable)pending, in_progress, queued, waiting, requested -> still runningClassify each workflow run by status (from gh run list):
in_progress, queued, waiting, requested, pending -> still runningcompleted -> done (look at conclusion)Same-SHA duplicate runs are normal. A same-repo PR fires both push and pull_request events, and if the workflow's concurrency.group formula resolves to different keys for the two events, both runs execute in parallel for the same commit. Wait for the latest one -- the older one will either be cancelled by concurrency or both will land in gh pr checks as redundant entries for the same workflow.
Decide:
pass/skipping in checks AND zero in-progress runs in gh run list for the current HEAD SHA -> STOP. Report success and the PR URL.gh pr checks has pending OR gh run list has any non-completed runs for the current SHA -> go to step 3 (wait). The run-list pending signal MUST gate "done" even if gh pr checks is briefly all-pass.Poll every 5 minutes, fixed interval. No backoff -- the user explicitly wants a 5-minute cadence so failures surface fast. Use a persistent Monitor with a description that starts with PR watch: so step 0 of a future invocation can find and stop it.
Example (both signals -- gh pr checks for failures, gh run list to catch runs that haven't yet registered as checks):
Monitor:
description: "PR watch: PR #1903 CI"
persistent: true
command: |
while true; do
checks="$(gh pr checks 1903 --json name,bucket 2>/dev/null || echo '[]')"
runs="$(gh run list --branch BRANCH --limit 20 --json status,conclusion,name 2>/dev/null || echo '[]')"
counts="$(jq -r '[.[] | .bucket] | group_by(.) | map("\(.[0])=\(length)") | join(" ")' <<<"$checks")"
pending_checks="$(jq -r '[.[] | select(.bucket=="pending")] | length' <<<"$checks")"
pending_runs="$(jq -r '[.[] | select(.status=="in_progress" or .status=="queued" or .status=="requested" or .status=="waiting" or .status=="pending")] | length' <<<"$runs")"
fail_now="$(jq -r '[.[] | select(.bucket=="fail" or .bucket=="cancel") | .name] | sort | join(",")' <<<"$checks")"
# Emit on new failures
if [ -n "$fail_now" ] && [ "$fail_now" != "${prev_fail:-}" ]; then
echo "[failures] $fail_now ($counts)"
prev_fail="$fail_now"
fi
# Done condition: BOTH check-pending = 0 AND run-list-pending = 0
# (run-list catches workflows that haven't yet registered as checks)
if [ "$pending_checks" = "0" ] && [ "$pending_runs" = "0" ]; then
echo "[final] checks: $counts | runs: 0 in-progress"
break
fi
sleep 300
done
Replace BRANCH with the actual branch name when instantiating the monitor.
The monitor emits notifications only on state changes (new failures or completion). It does not emit a notification every 5 minutes -- that would be noise. If the user wants a heartbeat, they can ask.
If the same check has been pending for more than 90 minutes total without a state change, the monitor must emit a [stalled] event and the agent should ask the user whether to keep waiting. (MegaLinter + nuts on Windows can legitimately take 20-40 min; don't panic before 90.)
Do not poll faster than 5 minutes -- it wastes API quota and produces no signal.
For each failing check, get its run + job IDs:
RUN_ID="$(gh pr checks "$PR_NUMBER" --json name,bucket,link \
| jq -r '.[] | select(.bucket=="fail") | .link' \
| sed 's|.*/runs/||; s|/job/.*||' | head -1)"
gh run view "$RUN_ID" --log-failed > /tmp/pr-watch-fail.log
Then read the tail of /tmp/pr-watch-fail.log and grep for the first concrete error. Don't read the whole log if it's huge -- find the actionable line. Useful greps for sfdx-hardis:
error TS -- TypeScript compile errors (tsc)error / ✖ -- ESLint rule failuresFAIL / ● -- Jest test failures and assertion blocksCannot find module -- missing import path (often a missing .js extension; see coding-conventions)JSCPD / COPYPASTE -- jscpd clone detection (use the fix-jscpd skill)trivy / CVE- -- security scan (use the fix-security-issue skill)Missing message / locale key -- i18n key absent from one of the 9 locale JSONs (use the i18n-usage skill)If multiple jobs fail with different errors, handle them in this order: build/compile (yarn compile) -> unit tests (yarn test:only) -> nuts (yarn test:nuts) -> lint (ESLint, MegaLinter) -> docs build -> security scan. Group jobs that fail with the same error and treat them as one fix.
Apply the "can I fix this cleanly?" test before editing:
.js import suffix, missing type, signature driftyarn lintfix-jscpd skill's decision framework -- refactor or add /* jscpd:ignore-start */en, de, es, fr, it, ja, nl, pl, pt-BR), alphabetically sorted; see i18n-usage skillfix-security-issue skill -- upgrade first, ignore only with justificationconfig/sfdx-hardis.jsonschema.jsonIf any answer is "no", ASK THE USER via AskUserQuestion instead of guessing:
Specifically STOP and ask when:
lib/ compiled output, docs/commands/** autogenerated by yarn build:doc, messages/*.md if generated, yarn.lock you didn't intend to touch) -- fix the source insteadnuts job on a fork PR with a secret/auth error -- this is expected; tests cannot run with org credentials on forks. Confirm with the user before treating it as a real failure.src/ (commands in src/commands/hardis/**/*.ts, shared utils in src/common/, i18n in src/i18n/<locale>.json, config schema in config/sfdx-hardis.jsonschema.json, workflows in .github/workflows/).claude/rules/coding-conventions.md and .claude/rules/i18n.md (ESM .js import suffixes, uxLog for logging, t() for user-visible strings, 9-locale parity, etc.)yarn compile -- TypeScript compilationyarn lint -- ESLintyarn test:only -- unit tests (CI-gated; skip if it requires CI=true and that's not feasible locally)yarn build:doc if you changed command description / flags / examples -- regenerates docs/|| true, weakening assertions, broad jscpd ignores) just to make CI green -- fix the root causeyarn build and commit the generated lib/ -- the build step is for CI/publish, not for the PR diffnpm install (would corrupt the lockfile)git status --short
git add <specific files> # never `git add -A`
git commit -m "$(cat <<'EOF'
Fix CI: <one-line summary of the failure>
<optional 1-2 line body if non-obvious>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Before pushing, reconcile with origin. The MegaLinter auto-fix workflow pushes commits titled [MegaLinter] Apply linters fixes onto the PR branch (see .github/workflows/mega-linter.yml, commit_user_name: megalinter-bot). Detect and handle:
git fetch origin "$BRANCH"
NEW_REMOTE_COMMITS="$(git log --format='%s' HEAD..origin/"$BRANCH")"
if printf '%s\n' "$NEW_REMOTE_COMMITS" | grep -q '^\[MegaLinter\] Apply linters fixes'; then
# Auto-fix bot pushed -- try to rebase onto it (keeps the bot's fixes)
if git pull --rebase origin "$BRANCH"; then
# Amend the bot commit to add an emoji prefix, then push to re-trigger workflows
_amend_megalinter_bot_commit_and_push "$BRANCH"
else
# Rebase failed with conflicts -- abort and force-push our commit
git rebase --abort
git push --force-with-lease
fi
else
git push
fi
Amend the MegaLinter bot commit with an emoji to re-trigger CI workflows. The auto-fix commit often lands without triggering all required checks; amending its subject line forces a new workflow run:
_amend_megalinter_bot_commit_and_push() {
local branch="$1"
local orig_msg
orig_msg="$(git log -1 --format='%s')"
# Only amend if it's the bot commit (idempotent -- skip if emoji already present)
if printf '%s' "$orig_msg" | grep -q '^\[MegaLinter\]' && ! printf '%s' "$orig_msg" | grep -qP '^[\x{1F300}-\x{1FFFF}]'; then
git commit --amend -m "$(printf 'Robot %s\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>' "$orig_msg")"
git push --force-with-lease
else
git push
fi
}
Notes:
--force-with-lease (not --force) so we refuse to overwrite anything we haven't seen, except the bot commit we just observedNEW_REMOTE_COMMITS contains commits that are not from the MegaLinter bot, STOP and ask the user -- someone else pushed work, don't silently overwrite--no-verify to bypass them -- fix the underlying issue (this matches the global commit safety protocol)After the push, capture the new HEAD SHA so you can wait for its workflow runs (not the previous ones). GitHub takes ~30s to register new runs; sleep 60 before re-entering step 2.
Go back to step 1. The loop ends when:
Each time you wake from a poll or finish a fix cycle, give the user one short line:
Cycle 2: linux-unit-tests failed (TS2345 in src/common/utils/i18n.ts:42), pushed e0a44f1. Waiting 5m.
Do not paste full job logs into the conversation. Summarize and link to the run.
git push is the only network-mutating action -- confirm the branch is not main/master before pushing[MegaLinter] Apply linters fixes commit landed on origin and rebasing onto it produces conflicts. Use --force-with-lease, never --force. Any other force-push needs explicit user permission.git fetch shows commits on origin that are not the MegaLinter bot, stop and ask -- someone else is working on the branchlib/ (TS output), don't hand-edit docs/commands/** (regenerate with yarn build:doc), don't edit yarn.lock directly (use yarn add/yarn upgrade)--no-verifygh is not authenticated or the repo isn't a GitHub repo, STOP and tell the user