@@ -43,25 +43,25 @@ jobs:
4343 group : fullsend-dispatch-${{ github.event.issue.number || github.event.pull_request.number }}
4444 cancel-in-progress : false
4545 if : >-
46- github.event_name != 'issue_comment'
47- || github.event.comment.user.type != 'Bot'
46+ (github.event_name != 'pull_request_target' && github.event_name != 'pull_request_review'
47+ || github.event.pull_request.head.ref != 'fullsend/scaffold-install')
48+ && (github.event_name != 'issue_comment'
49+ || github.event.comment.user.type != 'Bot')
4850 uses : fullsend-ai/.fullsend/.github/workflows/dispatch.yml@main
4951 with :
5052 event_action : ${{ github.event.action }}
5153
5254 stop-fix :
55+ # Job-level if: is intentionally coarse — it only screens for the
56+ # /fs-fix-stop command on a PR from a non-bot. The authoritative
57+ # authorization decision (collaborator permission API + PR-author escape
58+ # hatch) is made in the step below, so a maintainer whose author_association
59+ # is not MEMBER (e.g. private org membership) is not filtered out (ADR 0054).
5360 if : >-
5461 github.event_name == 'issue_comment'
5562 && github.event.issue.pull_request
5663 && github.event.comment.user.type != 'Bot'
5764 && github.event.comment.body == '/fs-fix-stop'
58- && (
59- github.event.comment.author_association == 'OWNER'
60- || github.event.comment.author_association == 'MEMBER'
61- || github.event.comment.author_association == 'COLLABORATOR'
62- || github.event.comment.author_association == 'CONTRIBUTOR'
63- || github.event.comment.user.login == github.event.issue.user.login
64- )
6565 runs-on : ubuntu-24.04
6666 permissions :
6767 contents : read
7373 GH_TOKEN : ${{ github.token }}
7474 PR_NUMBER : ${{ github.event.issue.number }}
7575 REPO : ${{ github.repository }}
76+ COMMENT_USER_LOGIN : ${{ github.event.comment.user.login }}
77+ ISSUE_USER_LOGIN : ${{ github.event.issue.user.login }}
7678 run : |
79+ set -euo pipefail
80+ # ADR 0054: authorize via the collaborator permission API
81+ # (admin|maintain|write), not author_association — the latter grants
82+ # contributor status to anyone with a single merged PR (issue #5421).
83+ # Mirrors has_repo_permission() in dispatch.yml; keep the two in sync.
84+ # The PR author may always stop the fix agent on their own PR.
85+ authorized=false
86+ if [[ -n "$COMMENT_USER_LOGIN" && "$COMMENT_USER_LOGIN" == "$ISSUE_USER_LOGIN" ]]; then
87+ authorized=true
88+ else
89+ if api_err=$(mktemp); then
90+ if role=$(gh api "repos/$REPO/collaborators/$COMMENT_USER_LOGIN/permission" \
91+ --jq '.role_name' 2>"$api_err"); then
92+ case "$role" in
93+ admin|maintain|write) authorized=true ;;
94+ esac
95+ else
96+ echo "::warning::Permission API call failed for $COMMENT_USER_LOGIN: $(cat "$api_err")"
97+ fi
98+ rm -f "$api_err"
99+ else
100+ echo "::warning::Failed to create temp file for permission check of $COMMENT_USER_LOGIN"
101+ fi
102+ fi
103+ if [[ "$authorized" != "true" ]]; then
104+ echo "::notice::User $COMMENT_USER_LOGIN is not authorized to stop the fix agent (requires write access or PR authorship)"
105+ exit 0
106+ fi
77107 gh label create "fullsend-no-fix" --repo "$REPO" \
78108 --description "Skip bot-triggered fix agent runs" --color "FBCA04" \
79109 --force 2>/dev/null || true
0 commit comments