Skip to content
Merged
98 changes: 95 additions & 3 deletions .github/workflows/cla-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jobs:
should_run_cla: ${{ steps.check-membership.outputs.should_run_cla }}
exempt_users: ${{ steps.check-membership.outputs.exempt_users }}
exempt_users_csv: ${{ steps.check-membership.outputs.exempt_users_csv }}
needs_cla_csv: ${{ steps.check-membership.outputs.needs_cla_csv }}
steps:
- name: Debug Event Context
run: |
Expand Down Expand Up @@ -221,6 +222,14 @@ jobs:
fi
echo "exempt_users_csv=$EXEMPT_USERNAMES_CSV" >> "$GITHUB_OUTPUT"

# Output users who need CLA as comma-separated string
if [ ${#NEEDS_CLA[@]} -eq 0 ]; then
NEEDS_CLA_CSV=""
else
NEEDS_CLA_CSV="$(IFS=','; echo "${NEEDS_CLA[*]}")"

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs_cla_csv is built directly from the NEEDS_CLA array, which can contain the sentinel value "<unknown>" (added earlier when a commit has no linked GitHub login). This will propagate into downstream mentions (e.g., @<unknown>) and into the CLA Assistant comment template. Consider filtering out non-mentionable placeholders (or mapping them to something non-@mention) before writing needs_cla_csv to $GITHUB_OUTPUT.

Suggested change
# Output users who need CLA as comma-separated string
if [ ${#NEEDS_CLA[@]} -eq 0 ]; then
NEEDS_CLA_CSV=""
else
NEEDS_CLA_CSV="$(IFS=','; echo "${NEEDS_CLA[*]}")"
# Output users who need CLA as comma-separated string, excluding
# non-mentionable placeholders such as "<unknown>"
NEEDS_CLA_MENTIONABLE=()
for user in "${NEEDS_CLA[@]}"; do
if [ "$user" != "<unknown>" ]; then
NEEDS_CLA_MENTIONABLE+=("$user")
fi
done
if [ ${#NEEDS_CLA_MENTIONABLE[@]} -eq 0 ]; then
NEEDS_CLA_CSV=""
else
NEEDS_CLA_CSV="$(IFS=','; echo "${NEEDS_CLA_MENTIONABLE[*]}")"

Copilot uses AI. Check for mistakes.
fi
echo "needs_cla_csv=$NEEDS_CLA_CSV" >> "$GITHUB_OUTPUT"

if [ ${#NEEDS_CLA[@]} -eq 0 ]; then
echo "All committers are org members or allowed bots; CLA not required"
echo "is_member=true" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -307,6 +316,89 @@ jobs:
exit 1
fi

- name: Post Multi-Author CLA Comment
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
Comment on lines +333 to +337

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step uses github.rest.issues.listComments / createComment / updateComment, which require the workflow token to have issues: read/write permission. The reusable workflow currently only declares pull-requests: write (no issues permission), so this step may fail with 403 when it tries to manage PR comments. Add issues: write to the workflow permissions: (or switch to an API that matches the granted permissions).

Copilot uses AI. Check for mistakes.
// Only run for pull_request_target events or issue_comment events on PRs
if (context.eventName === 'issue_comment' && !context.payload.issue?.pull_request) {
console.log('issue_comment is on a regular issue (not a PR), skipping');
return;
}
if (context.eventName !== 'pull_request_target' && context.eventName !== 'issue_comment') {
console.log(`Event ${context.eventName} not applicable for CLA comment, skipping`);
return;
}

const needsCsvRaw = '${{ needs.check-cla.outputs.needs_cla_csv }}';
if (!needsCsvRaw) {
console.log('No users need CLA, skipping comment');
return;
}

const users = needsCsvRaw.split(',').map(u => u.trim()).filter(Boolean);
if (users.length === 0) {
console.log('No users need CLA, skipping comment');
return;
}

const prNumber = context.payload.pull_request?.number || context.payload.issue?.number;
if (!prNumber) {
console.log('No PR number found, skipping comment');
return;
}

const repoName = '${{ inputs.repo_name }}';
const claUrl = '${{ steps.cla-url.outputs.url }}';
const mentions = users.map(u => `@${u}`).join(', ');
const MARKER = '<!-- cla-multi-author-check -->';

const body = `${MARKER}
👋 Hey ${mentions},

## Thanks for your contribution to \`${repoName}\`! 🧵

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the template literal, the backticks around repoName are written as \\`` (double backslash). In a JS template string this will typically render an extra literal backslash in the final comment (e.g., `codeweaver`). Consider using a single escape (`) so the output is proper Markdown inline code (`` ...` ``) without stray backslashes.

Copilot uses AI. Check for mistakes.
### You need to agree to the CLA first... 🖊️

Before we can accept your contribution, **you (each of you) need to agree to our Contributor License Agreement (CLA)**.

### To agree to the CLA, please comment:

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step uses needs_cla_csv (users who are not exempt) as the list to ping. That list doesn't reflect who has already signed the CLA, so after all contributors have signed, this marker comment will still be updated to say they “need to agree” on subsequent runs (e.g., new commits / reruns), which can be confusing. Consider either (a) wording the comment as “If you haven’t already signed…” or (b) deriving an actual “not signed” list by reading the signatures file (using CLA_ACCESS_TOKEN) and filtering out already-signed users before posting.

Suggested change
### You need to agree to the CLA first... 🖊️
Before we can accept your contribution, **you (each of you) need to agree to our Contributor License Agreement (CLA)**.
### To agree to the CLA, please comment:
### If you haven't already agreed to the CLA... 🖊️
Before we can accept your contribution, **please make sure you have agreed to our Contributor License Agreement (CLA)**. If you have already signed it, no further action is needed.
### If you still need to agree to the CLA, please comment:

Copilot uses AI. Check for mistakes.
> I read the contributors license agreement and I agree to it.

Those exact words are important[^1] — our bot needs them to recognize your agreement.

You can read the full CLA here: [Contributor License Agreement](${claUrl})

[^1]: Our bot needs those *exact* words to recognize that you agree to the CLA.`;

Comment thread
bashandbone marked this conversation as resolved.
Outdated
// Search for existing comment with marker
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});

const existingComment = comments.find(c => c.body.includes(MARKER));

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github.rest.issues.listComments is called without pagination (per_page) or github.paginate, so only the first page of comments is searched for the marker. If a PR has many comments, the existing marker comment may not be found and a duplicate comment will be created. Consider using github.paginate(github.rest.issues.listComments, { ... , per_page: 100 }) (or manual pagination) before searching.

Copilot uses AI. Check for mistakes.
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body,
});
console.log(`Updated existing CLA comment (id=${existingComment.id})`);
Comment thread
bashandbone marked this conversation as resolved.
Outdated
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
console.log('Posted new CLA comment');
}

- name: CLA Assistant
uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08
env:
Expand Down Expand Up @@ -335,11 +427,11 @@ jobs:

# Commit messages
create-file-commit-message: 'chore: initialize CLA signatures for ${{ inputs.repo_name }}'
signed-commit-message: '${{ github.event.pull_request.user.login || github.event.issue.user.login }} signed the CLA for ${{ inputs.repo_name }} PR #${{ github.event.pull_request.number || github.event.issue.number }}'
signed-commit-message: '${{ github.actor }} signed the CLA for ${{ inputs.repo_name }} PR #${{ github.event.pull_request.number || github.event.issue.number }}'

# Custom PR comments
custom-notsigned-prcomment: |
👋 Hey @${{ github.event.pull_request.user.login || github.event.issue.user.login }},
👋 Hey @${{ needs.check-cla.outputs.needs_cla_csv }},
Comment thread
bashandbone marked this conversation as resolved.
Outdated

## Thanks for your contribution to ${{ inputs.repo_name }}! 🧵

Expand All @@ -358,7 +450,7 @@ jobs:
[^1]: Our bot needs those *exact* words to recognize that you agree to the CLA.

custom-pr-sign-comment: |
✅ @${{ github.event.pull_request.user.login || github.event.issue.user.login }} has signed the CLA.
✅ @${{ github.actor }} has signed the CLA.

custom-allsigned-prcomment: |
## 🚀 All contributors have signed the CLA! 👍
Expand Down