Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8ec01fd
feat: implement issue #405 — petry-projects — workflow failures detec…
Jun 9, 2026
5948b52
fix(bot): address bot feedback [skip ci-relay]
donpetry-bot Jun 9, 2026
d12afaf
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 9, 2026
09232f8
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 9, 2026
b2d0121
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
d0d96a5
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
4c1cd72
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
9a6ebd5
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
8915b5a
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
5eb46e8
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
56923c0
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
fb0c23b
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
39d62fb
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
f1a99a4
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
99a3fc5
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
2a26aeb
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
8d21974
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
843053b
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
4a273d0
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
b58d955
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
059dd2e
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 10, 2026
8f3c09d
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
c1dfe85
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 10, 2026
66918d1
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 10, 2026
6638054
Merge branch 'main' into dev-lead/issue-405-20260609-2123
donpetry-bot Jun 10, 2026
6f7dde4
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 11, 2026
8bfa4d8
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 11, 2026
2538d25
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 11, 2026
151016e
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 11, 2026
1a6c15b
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 11, 2026
a058abe
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 11, 2026
4db18c7
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 11, 2026
8941ced
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 12, 2026
1c4b0a7
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 12, 2026
5c27618
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 12, 2026
7be6ddb
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 12, 2026
5bcfcb4
Merge branch 'main' into dev-lead/issue-405-20260609-2123
don-petry Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ on:
GH_PAT:
required: false

# ── Reusable-only (#497 self-host pinning) ───────────────────────────────
# Event-driven triggering now lives in the thin caller `pr-review-trigger.yml`,
# which invokes this workflow pinned to `pr-review.yml@pr-review/stable` and
# passes `agent_ref: pr-review/stable` so the agent's own scripts run at the
# pinned version too. Keeping this file reusable-only is what lets
# .github-private's OWN PR reviews run the known-good `stable` release — not
# whatever is on the branch under review — which is what breaks the
# self-hosting circular dependency. The job below still reads `github.event.*`
# (inherited from the caller's triggering event) and `inputs.*` (forwarded by
# the stub), so its behaviour is unchanged.
# ── Reusable-only (#497 self-host pinning) ───────────────────────────────
# Event-driven triggering now lives in the thin caller `pr-review-trigger.yml`,
# which invokes this workflow pinned to `pr-review.yml@pr-review/stable` and
# passes `agent_ref: pr-review/stable` so the agent's own scripts run at the
# pinned version too. Keeping this file reusable-only is what lets
# .github-private's OWN PR reviews run the known-good `stable` release — not
# whatever is on the branch under review — which is what breaks the
# self-hosting circular dependency. The job below still reads `github.event.*`
# (inherited from the caller's triggering event) and `inputs.*` (forwarded by
# the stub), so its behaviour is unchanged.

permissions:
contents: read
Expand Down
9 changes: 9 additions & 0 deletions scripts/dev-lead-intent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,15 @@ case "$EVENT_NAME" in
exit 0
fi

# Skip comments on closed/merged PRs — nothing to modify on a merged branch.
# This is a fast-path guard; checkout_pr_in_worktree provides a deeper safety
# net, but failing early here avoids spending tokens on a no-op run (issue #405).
pr_state=$(jq -r '.issue.state // empty' "$EVENT_PATH" 2>/dev/null || true)
if [[ "${pr_state:-}" == "closed" ]]; then
emit_skip "pr-already-closed"
exit 0
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

commenter=$(jq -r '.comment.user.login // empty' "$EVENT_PATH" 2>/dev/null || true)
comment_body=$(jq -r '.comment.body // empty' "$EVENT_PATH" 2>/dev/null || true)
pr_number=$(jq -r '.issue.number // empty' "$EVENT_PATH" 2>/dev/null || true)
Expand Down
22 changes: 20 additions & 2 deletions scripts/lib/pr-worktree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ PR_WORKTREE_AGENT_DIR=""
checkout_pr_in_worktree() {
local pr="$1" repo="$2"

# Exit cleanly when the PR is already merged or closed (issue #405).
# `gh pr checkout` fails with "fatal: couldn't find remote ref" when the
# branch was deleted after merging. Detect this early and skip gracefully.
# MUST run before the EXIT trap is registered — if exit 0 fires after the
# trap is set, the trap captures and replays the caller's EXIT handler
# prematurely (observable as bats reporting "Executed 2 instead of 1 tests").
local _pr_json=""
local _pr_state=""
local _pr_branch=""
_pr_json="$(gh pr view "$pr" --repo "$repo" --json state,headRefName 2>/dev/null || true)"
if [[ -n "$_pr_json" ]]; then
_pr_state="$(echo "$_pr_json" | jq -r '.state // empty' 2>/dev/null || true)"
_pr_branch="$(echo "$_pr_json" | jq -r '.headRefName // empty' 2>/dev/null || true)"
fi
if [[ -n "$_pr_state" && "$_pr_state" != "OPEN" ]]; then
echo "::notice::PR #${pr} is ${_pr_state} — skipping checkout (nothing to modify on a merged/closed PR)"
exit 0
fi
Comment thread
don-petry marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

PR_WORKTREE_AGENT_DIR="$(pwd)"

# mktemp -d gives us a private parent dir; the worktree leaf must NOT pre-exist
Expand Down Expand Up @@ -82,8 +101,7 @@ checkout_pr_in_worktree() {
# and release whatever worktree currently holds it: detach the main agent
# checkout in place (it cannot be removed), force-remove any other.
git worktree prune 2>/dev/null || true
local _pr_branch=""
_pr_branch="$(gh pr view "$pr" --repo "$repo" --json headRefName --jq '.headRefName' 2>/dev/null || true)"

if [ -n "$_pr_branch" ]; then
# Resolve the agent worktree's ROOT for comparison. `git worktree list`
# emits each worktree's top-level, fully-resolved path, but
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"_test_expected_intent": "skip",
"action": "created",
"issue": {
"number": 519,
"title": "feat(pr-review): mirror dispatch inputs as workflow_call inputs",
"state": "closed",
"pull_request": {
"url": "https://api.github.com/repos/petry-projects/.github-private/pulls/519",
"merged_at": "2026-06-09T21:00:00Z"
}
},
"comment": {
"id": 4001,
"body": "## Quality Gate passed\n\nNo new issues.",
"user": {
"login": "sonarqubecloud[bot]",
"type": "Bot"
}
},
"repository": { "full_name": "petry-projects/.github-private" },
"sender": { "login": "sonarqubecloud[bot]", "type": "Bot" }
}
14 changes: 14 additions & 0 deletions tests/dev-lead/unit/test_intent_reviews.bats
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,17 @@ EOF
[ "$status" -eq 0 ]
[ "$(_get_env INTENT_TYPE)" = "rebase" ]
}

@test "reviews: issue_comment trusted-bot on already-closed PR → skip pr-already-closed" {
# Regression guard for issue #405: a SonarQube comment on a merged PR used to
# trigger fix-bot-comment, which then crashed at checkout_pr_in_worktree because
# the branch was deleted. The classifier must emit skip before that point.
export GITHUB_EVENT_NAME="issue_comment"
export GITHUB_EVENT_PATH="$FIXTURES_DIR/issue_comment_sonarqube_closed_pr.json"

run bash "$INTENT_SCRIPT"

[ "$status" -eq 0 ]
[ "$(_get_env INTENT_TYPE)" = "skip" ]
[ "$(_get_env INTENT_REASON)" = "pr-already-closed" ]
}
50 changes: 43 additions & 7 deletions tests/dev-lead/unit/test_pr_worktree.bats
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ LIB="$SCRIPT_DIR/scripts/lib/pr-worktree.sh"

setup() {
STUB_BIN_DIR="$(mktemp -d)"
# PR_STATE_API controls the state returned by the gh stub's `pr view` JSON response.
# Set unconditionally so the runner environment cannot leak in and break unrelated tests.
export PR_STATE_API="OPEN"

# gh stub: `gh pr checkout <n>` switches the worktree to the PR branch,
# mirroring the real command (which is what made the old prompt files vanish).
# `gh pr view --json headRefName --jq .headRefName` returns the PR's head
# branch (`old` in this fixture) so the collision-prevention logic in
# checkout_pr_in_worktree can identify and release a stale holder (issue #470).
# `gh pr view` returns a JSON object with state and headRefName so the combined
# call in checkout_pr_in_worktree handles both the closed-PR guard (issue #405)
# and the stale-worktree release (issue #470) in a single round-trip.
cat > "$STUB_BIN_DIR/gh" <<'GHEOF'
#!/usr/bin/env bash
case "$1 $2" in
"pr checkout") git checkout -q old ;;
"pr view") echo "old" ;;
*) echo "{}" ;;
case "$*" in
"pr checkout"*) git checkout -q old ;;
"pr view"*) echo "{\"state\": \"${PR_STATE_API:-OPEN}\", \"headRefName\": \"old\"}" ;;
*) echo "{}" ;;
esac
GHEOF
Comment thread
don-petry marked this conversation as resolved.
chmod +x "$STUB_BIN_DIR/gh"
Expand Down Expand Up @@ -224,3 +228,35 @@ teardown() {
run git -C "$AGENT_REPO" symbolic-ref -q HEAD
[ "$status" -ne 0 ]
}

# --- issue #405: merged/closed PR early-exit guard ---------------------------
# When a trusted-bot comment arrives on an already-merged PR (branch deleted),
# checkout_pr_in_worktree must exit 0 cleanly rather than crashing with
# "fatal: couldn't find remote ref" from `gh pr checkout`.

@test "checkout_pr_in_worktree: exits cleanly when PR is already merged (MERGED state)" {
source "$LIB"
cd "$AGENT_REPO"
export PR_STATE_API="MERGED"

run checkout_pr_in_worktree 519 owner/repo

[ "$status" -eq 0 ]
[[ "$output" == *"PR #519 is MERGED"* ]]
[[ "$output" == *"skipping checkout"* ]]
# The agent repo must NOT have been switched to another branch by gh pr checkout.
[ "$(git -C "$AGENT_REPO" rev-parse --abbrev-ref HEAD)" = "main" ]
}

@test "checkout_pr_in_worktree: exits cleanly when PR is closed without merge (CLOSED state)" {
source "$LIB"
cd "$AGENT_REPO"
export PR_STATE_API="CLOSED"

run checkout_pr_in_worktree 99 owner/repo

[ "$status" -eq 0 ]
[[ "$output" == *"PR #99 is CLOSED"* ]]
[[ "$output" == *"skipping checkout"* ]]
[ "$(git -C "$AGENT_REPO" rev-parse --abbrev-ref HEAD)" = "main" ]
}
Loading