CAA: accept recheck, the recovery path the bot already advertises
#16
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # .github/workflows/cla.yml -- goes in EACH library repo. | |
| # Blocks a PR from merging until every human commit-author has signed the | |
| # Contributor Assignment Agreement (CAA). Signatures are recorded per GitHub | |
| # user in one central file, so a person signs once and it counts everywhere. | |
| # | |
| # Contributors sign by posting this exact comment ON THEIR PULL REQUEST: | |
| # I have read the CAA and I hereby sign it, assigning copyright in my | |
| # contributions to Más Bandwidth LLC. | |
| # | |
| # REQUIRES a repo or org secret named CLA_SIGNATURES_TOKEN (a fine-grained or | |
| # classic PAT with contents + pull-requests write on the library repos and on | |
| # mas-bandwidth/.github). The built-in GITHUB_TOKEN is read-only for pull | |
| # requests from forks, which is exactly the case we need to gate, so the PAT is | |
| # required. Do not commit the token, add it in repo/org Settings -> Secrets. | |
| # | |
| # --------------------------------------------------------------------------- | |
| # SCOPE AND LIMITS OF THIS AUTOMATION -- read before changing this file. | |
| # | |
| # contributor-assistant/github-action is PULL-REQUEST-ONLY BY DESIGN. The first | |
| # thing it does is a GraphQL query for `repository.pullRequest(number: N)` in | |
| # order to list that PR's commit authors; its whole data model is "which commit | |
| # authors on this PR have signed". A plain issue has no pull request and no | |
| # commits, so the query fails with | |
| # | |
| # graphql call to get the committers details failed: GraphqlError: | |
| # Could not resolve to a PullRequest with the number of N | |
| # | |
| # and the run dies there, before it ever looks at the comments. That is not a | |
| # misconfiguration on our side and it cannot be fixed from this file. It will | |
| # not be fixed upstream either: the action's repository is ARCHIVED and v2.6.1 | |
| # (2024-09-26) is the last release it will ever have. | |
| # | |
| # LIMIT 1 -- A SIGNATURE POSTED ON AN ISSUE IS NEVER RECORDED AUTOMATICALLY. | |
| # The check below is therefore gated to pull requests, so issue threads stop | |
| # showing a spurious failed run. A signature posted on an issue is still a valid | |
| # signature; it just has to be entered by hand (see RECORDING BY HAND). | |
| # | |
| # LIMIT 2 -- THE SIGNATURE TEST IS EXACT STRING EQUALITY. | |
| # Because `custom-pr-sign-comment` is set, the action matches signatures with | |
| # getCustomPrSignComment().toLowerCase() === comment.trim().toLowerCase() | |
| # (src/pullrequest/signatureComment.ts). Anything extra -- a footnote, a version | |
| # pin, a trailing remark -- is not a match. The action does have a permissive | |
| # "contains" mode, 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 ("I have read the CLA Document and I hereby sign the CLA"). Custom | |
| # wording and substring matching are mutually exclusive; we need our own | |
| # wording, so we are on the exact-equality branch and cannot leave it. | |
| # A signature carrying extra text is still valid under Más Bandwidth policy | |
| # (version-pinned signatures were expressly ruled acceptable on 2026-07-24), so | |
| # it too must be recorded by hand. | |
| # One narrow variant does recover on its own: the action trims and lowercases | |
| # both sides before comparing, while the gate below is exact, so a signature | |
| # differing only in capitalisation or surrounding whitespace satisfies the | |
| # action but never reaches it. Commenting `recheck` re-runs the action, which | |
| # then finds that comment and records it normally. | |
| # | |
| # The second step below exists only to make both of those cases LOUD instead of | |
| # silent. It records nothing and writes nowhere; it raises a warning so that a | |
| # maintainer notices and records the signature manually. | |
| # | |
| # RECORDING A SIGNATURE BY HAND | |
| # Append one object to signatures/caa.json on the `cla-signatures` branch of | |
| # mas-bandwidth/.github, in the same shape the action writes: | |
| # | |
| # {"name": "<github login>", "id": <numeric user id>, | |
| # "comment_id": <id of the signing comment>, | |
| # "created_at": "<comment created_at, ISO 8601>", | |
| # "repoId": <numeric repo id>, "pullRequestNo": <issue or PR number>} | |
| # | |
| # The ids come from the comment itself, e.g. | |
| # gh api repos/mas-bandwidth/<repo>/issues/<n>/comments \ | |
| # --jq '.[] | {comment_id: .id, name: .user.login, id: .user.id, | |
| # created_at}' | |
| # gh api repos/mas-bandwidth/<repo> --jq .id | |
| # Commit it with a message naming the signer and the thread, so the ledger's | |
| # history stays auditable. | |
| # | |
| # Then, if a pull request is sitting red waiting on that signature, comment | |
| # `recheck` on it. That re-runs the check against the updated ledger and turns | |
| # it green. Nothing else re-evaluates an already-open pull request short of | |
| # pushing another commit to it, and that is true of every route into the | |
| # ledger, not just manual ones: the ledger is org-wide, so a contributor who | |
| # signs on one pull request leaves any other open pull request of theirs red | |
| # until it is rechecked. | |
| # --------------------------------------------------------------------------- | |
| name: Contributor Assignment Agreement | |
| on: | |
| issue_comment: | |
| types: [created] | |
| pull_request_target: | |
| types: [opened, synchronize] | |
| permissions: | |
| actions: write | |
| contents: read | |
| pull-requests: write | |
| statuses: write | |
| jobs: | |
| caa: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Pull requests only. `github.event.issue.pull_request` is present only | |
| # when an issue_comment was posted on a PR; on a plain issue it is null | |
| # and the action would abort (see LIMIT 1 above). | |
| # | |
| # `recheck` re-runs the check; it records nothing by itself, it just makes | |
| # the action re-read this PR's commit authors and the ledger and restate | |
| # the verdict. It has to be accepted here because the action advertises it | |
| # unprompted -- `suggest-recheck` defaults to true, so every "please sign" | |
| # comment it posts ends with "You can retrigger this bot by commenting | |
| # recheck in this Pull Request". Without this clause that instruction is | |
| # dead: the comment arrives, no `if` matches, the job never starts, and | |
| # the contributor gets no run, no log and no error. It is also the only | |
| # way to clear a red check after a signature is recorded by hand. | |
| - name: CAA check | |
| if: >- | |
| github.event_name == 'pull_request_target' || | |
| (github.event.issue.pull_request != null && | |
| (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.comment.body == 'recheck')) | |
| uses: contributor-assistant/github-action@v2.6.1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_SIGNATURES_TOKEN }} | |
| with: | |
| # one central signature store for the whole org -> per-user tracking | |
| remote-organization-name: mas-bandwidth | |
| remote-repository-name: .github | |
| branch: cla-signatures | |
| path-to-signatures: signatures/caa.json | |
| path-to-document: https://github.com/mas-bandwidth/.github/blob/main/CAA.md | |
| # our own accounts and bots never need to sign | |
| allowlist: gafferongames,rowan-claude,*[bot] | |
| custom-notsigned-prcomment: 'Thanks for the contribution. Before it can be merged, please read the [Contributor Assignment Agreement](https://github.com/mas-bandwidth/.github/blob/main/CAA.md) and sign it by posting the exact sentence below as a comment on this PR.' | |
| custom-pr-sign-comment: 'I have read the CAA and I hereby sign it, assigning copyright in my contributions to Más Bandwidth LLC.' | |
| custom-allsigned-prcomment: 'All contributors have signed the CAA. Thank you.' | |
| lock-pullrequest-aftermerge: false | |
| # Fires when a comment reads like a CAA signature but will not be picked | |
| # up by the step above -- either it is on an issue rather than a pull | |
| # request (LIMIT 1), or it carries extra text and so fails the action's | |
| # exact-equality test (LIMIT 2). This step records nothing anywhere. It | |
| # only raises a warning, so that a signature is never lost silently. | |
| # Bots are skipped because the action's own "please sign" comment quotes | |
| # the signature sentence verbatim; the two accounts below are skipped for | |
| # the same reason as in `allowlist` above -- they are ours, and they quote | |
| # signatures back when replying by email. | |
| - name: Flag a signature the automation cannot record | |
| if: >- | |
| github.event_name == 'issue_comment' && | |
| github.event.comment.user.type != 'Bot' && | |
| github.event.comment.user.login != 'gafferongames' && | |
| github.event.comment.user.login != 'rowan-claude' && | |
| contains(github.event.comment.body, 'I hereby sign it, assigning copyright') && | |
| !(github.event.issue.pull_request != null && | |
| github.event.comment.body == 'I have read the CAA and I hereby sign it, assigning copyright in my contributions to Más Bandwidth LLC.') | |
| env: | |
| # via env, never inlined into the script: comment bodies are untrusted | |
| SIGNER: ${{ github.event.comment.user.login }} | |
| SIGNER_ID: ${{ github.event.comment.user.id }} | |
| COMMENT_ID: ${{ github.event.comment.id }} | |
| COMMENT_AT: ${{ github.event.comment.created_at }} | |
| COMMENT_URL: ${{ github.event.comment.html_url }} | |
| REPO_ID: ${{ github.event.repository.id }} | |
| THREAD_NO: ${{ github.event.issue.number }} | |
| IS_PR: ${{ github.event.issue.pull_request != null }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$IS_PR" = "true" ]; then | |
| why="it carries text beyond the exact signature sentence" | |
| else | |
| why="it was posted on an issue rather than a pull request" | |
| fi | |
| 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' \ | |
| "$SIGNER" "$why" "$COMMENT_URL" | |
| { | |
| echo "### CAA signature needs manual recording" | |
| echo | |
| echo "\`$SIGNER\` appears to have signed the CAA, but the signature was **not** recorded automatically because $why." | |
| echo | |
| echo "Comment: $COMMENT_URL" | |
| echo | |
| echo "Review the comment. If it is a genuine signature, append this to" | |
| echo "\`signatures/caa.json\` on the \`cla-signatures\` branch of \`mas-bandwidth/.github\`:" | |
| echo | |
| echo '```json' | |
| echo "{" | |
| echo " \"name\": \"$SIGNER\"," | |
| echo " \"id\": $SIGNER_ID," | |
| echo " \"comment_id\": $COMMENT_ID," | |
| echo " \"created_at\": \"$COMMENT_AT\"," | |
| echo " \"repoId\": $REPO_ID," | |
| echo " \"pullRequestNo\": $THREAD_NO" | |
| echo "}" | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" |