Skip to content

Commit 809a70c

Browse files
rowan-claudeclaude
andauthored
CAA: stop failing on issue threads, and stop dropping signatures silently (#162)
The Contributor Assignment Agreement workflow has two defects, and both have already cost us real signatures. 1. It failed on every signature posted on an issue. contributor-assistant/github-action is pull-request-only by design. It opens with a GraphQL query for repository.pullRequest(number: N) to list that PR's commit authors -- its entire data model is "which commit authors on this PR have signed". On a plain issue that query fails with Could not resolve to a PullRequest with the number of N and the run dies there, before the action ever reads the comments. All four CAA outreach threads are issues, so every signature posted on one produced a red run and recorded nothing. The check is now gated to pull requests, using github.event.issue.pull_request. 2. A signature carrying any extra text was ignored while the run went green. Both the workflow's own `if:` gate and the action's internal matcher test for exact string equality (custom.toLowerCase() === body.trim().toLowerCase(), in src/pullrequest/signatureComment.ts). A signature with a footnote or a version pin matches neither, so the step was skipped and the job reported success having recorded nothing at all. Neither defect can be fixed inside the action. Its permissive "contains" matcher exists, but it is reachable only when custom-pr-sign-comment is left empty, and its pattern is hardcoded to CLA Assistant's own default wording -- custom wording and substring matching are mutually exclusive, and we need our own wording. Nor is a fix coming from upstream: that repository is archived and v2.6.1 is the last release it will ever have. So this change does not pretend to fix the matching. It makes the failure loud instead of silent. A new step detects a comment that reads like a CAA signature but will not be recorded -- because it is on an issue, or because it carries extra text -- and raises a warning plus a job summary containing the exact JSON record a maintainer needs. That step writes nothing and records nothing itself. The signature ledger stays a human-curated legal record. The header comment now documents both limits and the manual recording path. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 139beae commit 809a70c

1 file changed

Lines changed: 125 additions & 2 deletions

File tree

.github/workflows/cla.yml

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Contributor Assignment Agreement (CAA). Signatures are recorded per GitHub
44
# user in one central file, so a person signs once and it counts everywhere.
55
#
6-
# Contributors sign by posting this exact comment on their PR:
6+
# Contributors sign by posting this exact comment ON THEIR PULL REQUEST:
77
# I have read the CAA and I hereby sign it, assigning copyright in my
88
# contributions to Más Bandwidth LLC.
99
#
@@ -12,6 +12,64 @@
1212
# mas-bandwidth/.github). The built-in GITHUB_TOKEN is read-only for pull
1313
# requests from forks, which is exactly the case we need to gate, so the PAT is
1414
# required. Do not commit the token, add it in repo/org Settings -> Secrets.
15+
#
16+
# ---------------------------------------------------------------------------
17+
# SCOPE AND LIMITS OF THIS AUTOMATION -- read before changing this file.
18+
#
19+
# contributor-assistant/github-action is PULL-REQUEST-ONLY BY DESIGN. The first
20+
# thing it does is a GraphQL query for `repository.pullRequest(number: N)` in
21+
# order to list that PR's commit authors; its whole data model is "which commit
22+
# authors on this PR have signed". A plain issue has no pull request and no
23+
# commits, so the query fails with
24+
#
25+
# graphql call to get the committers details failed: GraphqlError:
26+
# Could not resolve to a PullRequest with the number of N
27+
#
28+
# and the run dies there, before it ever looks at the comments. That is not a
29+
# misconfiguration on our side and it cannot be fixed from this file. It will
30+
# not be fixed upstream either: the action's repository is ARCHIVED and v2.6.1
31+
# (2024-09-26) is the last release it will ever have.
32+
#
33+
# LIMIT 1 -- A SIGNATURE POSTED ON AN ISSUE IS NEVER RECORDED AUTOMATICALLY.
34+
# The check below is therefore gated to pull requests, so issue threads stop
35+
# showing a spurious failed run. A signature posted on an issue is still a valid
36+
# signature; it just has to be entered by hand (see RECORDING BY HAND).
37+
#
38+
# LIMIT 2 -- THE SIGNATURE TEST IS EXACT STRING EQUALITY.
39+
# Because `custom-pr-sign-comment` is set, the action matches signatures with
40+
# getCustomPrSignComment().toLowerCase() === comment.trim().toLowerCase()
41+
# (src/pullrequest/signatureComment.ts). Anything extra -- a footnote, a version
42+
# pin, a trailing remark -- is not a match. The action does have a permissive
43+
# "contains" mode, but it is reachable only when `custom-pr-sign-comment` is
44+
# left empty, and its pattern is hardcoded to CLA Assistant's own default
45+
# wording ("I have read the CLA Document and I hereby sign the CLA"). Custom
46+
# wording and substring matching are mutually exclusive; we need our own
47+
# wording, so we are on the exact-equality branch and cannot leave it.
48+
# A signature carrying extra text is still valid under Más Bandwidth policy
49+
# (version-pinned signatures were expressly ruled acceptable on 2026-07-24), so
50+
# it too must be recorded by hand.
51+
#
52+
# The second step below exists only to make both of those cases LOUD instead of
53+
# silent. It records nothing and writes nowhere; it raises a warning so that a
54+
# maintainer notices and records the signature manually.
55+
#
56+
# RECORDING A SIGNATURE BY HAND
57+
# Append one object to signatures/caa.json on the `cla-signatures` branch of
58+
# mas-bandwidth/.github, in the same shape the action writes:
59+
#
60+
# {"name": "<github login>", "id": <numeric user id>,
61+
# "comment_id": <id of the signing comment>,
62+
# "created_at": "<comment created_at, ISO 8601>",
63+
# "repoId": <numeric repo id>, "pullRequestNo": <issue or PR number>}
64+
#
65+
# The ids come from the comment itself, e.g.
66+
# gh api repos/mas-bandwidth/<repo>/issues/<n>/comments \
67+
# --jq '.[] | {comment_id: .id, name: .user.login, id: .user.id,
68+
# created_at}'
69+
# gh api repos/mas-bandwidth/<repo> --jq .id
70+
# Commit it with a message naming the signer and the thread, so the ledger's
71+
# history stays auditable.
72+
# ---------------------------------------------------------------------------
1573

1674
name: Contributor Assignment Agreement
1775
on:
@@ -30,8 +88,14 @@ jobs:
3088
caa:
3189
runs-on: ubuntu-latest
3290
steps:
91+
# Pull requests only. `github.event.issue.pull_request` is present only
92+
# when an issue_comment was posted on a PR; on a plain issue it is null
93+
# and the action would abort (see LIMIT 1 above).
3394
- name: CAA check
34-
if: (github.event.comment.body == 'I have read the CAA and I hereby sign it, assigning copyright in my contributions to Más Bandwidth LLC.') || github.event_name == 'pull_request_target'
95+
if: >-
96+
github.event_name == 'pull_request_target' ||
97+
(github.event.issue.pull_request != null &&
98+
github.event.comment.body == 'I have read the CAA and I hereby sign it, assigning copyright in my contributions to Más Bandwidth LLC.')
3599
uses: contributor-assistant/github-action@v2.6.1
36100
env:
37101
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -49,3 +113,62 @@ jobs:
49113
custom-pr-sign-comment: 'I have read the CAA and I hereby sign it, assigning copyright in my contributions to Más Bandwidth LLC.'
50114
custom-allsigned-prcomment: 'All contributors have signed the CAA. Thank you.'
51115
lock-pullrequest-aftermerge: false
116+
117+
# Fires when a comment reads like a CAA signature but will not be picked
118+
# up by the step above -- either it is on an issue rather than a pull
119+
# request (LIMIT 1), or it carries extra text and so fails the action's
120+
# exact-equality test (LIMIT 2). This step records nothing anywhere. It
121+
# only raises a warning, so that a signature is never lost silently.
122+
# Bots are skipped because the action's own "please sign" comment quotes
123+
# the signature sentence verbatim; the two accounts below are skipped for
124+
# the same reason as in `allowlist` above -- they are ours, and they quote
125+
# signatures back when replying by email.
126+
- name: Flag a signature the automation cannot record
127+
if: >-
128+
github.event_name == 'issue_comment' &&
129+
github.event.comment.user.type != 'Bot' &&
130+
github.event.comment.user.login != 'gafferongames' &&
131+
github.event.comment.user.login != 'rowan-claude' &&
132+
contains(github.event.comment.body, 'I hereby sign it, assigning copyright') &&
133+
!(github.event.issue.pull_request != null &&
134+
github.event.comment.body == 'I have read the CAA and I hereby sign it, assigning copyright in my contributions to Más Bandwidth LLC.')
135+
env:
136+
# via env, never inlined into the script: comment bodies are untrusted
137+
SIGNER: ${{ github.event.comment.user.login }}
138+
SIGNER_ID: ${{ github.event.comment.user.id }}
139+
COMMENT_ID: ${{ github.event.comment.id }}
140+
COMMENT_AT: ${{ github.event.comment.created_at }}
141+
COMMENT_URL: ${{ github.event.comment.html_url }}
142+
REPO_ID: ${{ github.event.repository.id }}
143+
THREAD_NO: ${{ github.event.issue.number }}
144+
IS_PR: ${{ github.event.issue.pull_request != null }}
145+
run: |
146+
set -euo pipefail
147+
if [ "$IS_PR" = "true" ]; then
148+
why="it carries text beyond the exact signature sentence"
149+
else
150+
why="it was posted on an issue rather than a pull request"
151+
fi
152+
printf '::warning title=CAA signature needs manual recording::%s appears to have signed the CAA, but it was NOT recorded automatically because %s. See %s\n' \
153+
"$SIGNER" "$why" "$COMMENT_URL"
154+
{
155+
echo "### CAA signature needs manual recording"
156+
echo
157+
echo "\`$SIGNER\` appears to have signed the CAA, but the signature was **not** recorded automatically because $why."
158+
echo
159+
echo "Comment: $COMMENT_URL"
160+
echo
161+
echo "Review the comment. If it is a genuine signature, append this to"
162+
echo "\`signatures/caa.json\` on the \`cla-signatures\` branch of \`mas-bandwidth/.github\`:"
163+
echo
164+
echo '```json'
165+
echo "{"
166+
echo " \"name\": \"$SIGNER\","
167+
echo " \"id\": $SIGNER_ID,"
168+
echo " \"comment_id\": $COMMENT_ID,"
169+
echo " \"created_at\": \"$COMMENT_AT\","
170+
echo " \"repoId\": $REPO_ID,"
171+
echo " \"pullRequestNo\": $THREAD_NO"
172+
echo "}"
173+
echo '```'
174+
} >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)