Skip to content

yjs/cla-tool

Repository files navigation

cla-tool

A small Contributor License Agreement (CLA) bot for GitHub, packaged as a reusable Action. It asks each contributor to sign a short agreement on their pull request before it can be merged, and remembers who has signed.

It was built for yjs/yhub and is intentionally simple: a few hundred lines of plain ESM JavaScript on top of fetch and lib0 — no Octokit, no server to run.

What's a CLA, and why does this exist?

A Contributor License Agreement is a short agreement where a contributor confirms two simple things: "I wrote this" and "I give the project permission to use and license it." That's it.

Here's the why, plainly: we want yhub to be genuinely open source under the AGPL. We also want to be able to sell a proprietary license (and proprietary plugins) to companies who can't or don't want to use the AGPL. That dual model — free and open for everyone, paid for those who need different terms — is, after a lot of thought, the best way we've found to make a living from open source and keep investing in it full-time.

To offer that proprietary option, we need each contributor's permission to include their code in both editions. Without it, a single outside contribution we couldn't relicense would block the proprietary edition. The CLA is simply how we get that permission, up front and on the record.

What you keep: you keep the copyright to your contribution — this is a license, not a transfer of ownership. And everything you contribute always stays available under the AGPL. The CLA just additionally lets us offer it under a commercial license too. We use the well-known Harmony agreement (Option Five) for exactly this; the full text is in CLA.md.

What it does

  • Runs as a GitHub Action on pull_request_target and issue_comment.
  • Works out which contributors on a PR need to sign (by their GitHub account).
  • Posts a single, tidy PR comment with a checklist and signing instructions.
  • Lets contributors sign by commenting a phrase on the PR.
  • Records signatures as JSON on a dedicated branch in your repo.
  • Sets a license/cla commit status so a branch-protection rule blocks merging until everyone has signed.

How signing works

The bot comments on the PR asking unsigned contributors to sign. A contributor signs by posting a comment containing exactly:

I have read the CLA Document and I hereby sign the CLA

The bot records it and flips the license/cla status to ✅. Comment recheck to re-evaluate (e.g. after fixing a commit author). Signatures are versioned by a hash of CLA.md: if you ever change the agreement text, everyone re-signs the new version.

Inspiration

This is inspired by CLA Assistant Lite, which is no longer maintained (archived in 2026). The signing flow and the same signatures file shape ({ "signedContributors": [...] }) are deliberately familiar. The main difference is how merges are gated: CLA Assistant Lite relied on the workflow run's own pass/fail and re-ran it with a Personal Access Token; cla-tool instead sets an explicit commit status on the PR head, so no PAT and no workflow re-run are needed.

Quick start (adding it to a repo)

  1. Add the workflow .github/workflows/cla.yml (see examples/yhub):

    name: CLA
    on:
      pull_request_target: { types: [opened, synchronize, reopened] }
      issue_comment:       { types: [created] }
    permissions:
      contents: write        # commit signatures to the signatures branch
      pull-requests: write   # post/update the sticky comment
      statuses: write        # set the license/cla commit status
    jobs:
      cla:
        if: >-
          github.event_name == 'pull_request_target' ||
          (github.event.issue.pull_request != null &&
           (contains(github.event.comment.body, 'I hereby sign the CLA') ||
            github.event.comment.body == 'recheck'))
        runs-on: ubuntu-latest
        steps:
          - uses: yjs/cla-tool@v1
            with:
              cla-document-path: CLA.md
              allowlist: dependabot[bot],github-actions[bot],dmonad
  2. Add the agreement CLA.md (and optionally CLA-ENTITY.md) and a CONTRIBUTING.md to your repo's default branch.

  3. Make the check required. In Settings → Branches → branch protection for main, enable Require status checks to pass and add license/cla.

  4. That's it. The cla-signatures branch is created automatically on the first signature (keep it unprotected). The default repo token is enough — you only need a PAT if you store signatures in a different repository.

The if: guard keeps the job from running on every comment and avoids interfering with other comment-triggered workflows. GitHub does not re-trigger workflows from comments the bot itself posts, so there is no signing loop.

Inputs

Input Default Description
github-token ${{ github.token }} Token to read the PR, comment, and set the status.
personal-access-token '' Only needed to store signatures in a different repo.
cla-document-path CLA.md Path (on the base branch) to the agreement text.
sign-phrase I have read the CLA Document and I hereby sign the CLA Phrase a contributor comments to sign (case-insensitive).
recheck-phrase recheck Comment this to re-evaluate without signing.
allowlist '' Comma/space-separated logins exempt from signing (bots are always exempt).
base-branch main Only enforce the CLA on PRs targeting this branch.
signatures-branch cla-signatures Unprotected branch holding the signatures file (auto-created).
signatures-path version1/cla.json Path of the signatures JSON on that branch.
status-context license/cla Commit-status name to set (make it a required check).

Signatures file

Stored on the cla-signatures branch at version1/cla.json:

{
  "signedContributors": [
    { "login": "alice", "id": 7, "pullRequestNo": 42, "commentId": 100, "createdAt": "2026-06-24T00:00:00Z", "claVersion": "e112683956a7…" }
  ]
}

claVersion is the SHA-256 of CLA.md, so a signature only counts for the exact text that was agreed to.

Security

The workflow uses pull_request_target, which runs with a write-capable token even for PRs from forks (that's why the bot can comment and set a status on outside contributions). cla-tool only ever reads PR metadata and writes a comment/status — it never checks out or runs the PR's code, which is the one thing you must avoid under pull_request_target.

Development

npm install
npm test          # lib0/testing unit + end-to-end (mocked GitHub) tests
npm run lint      # standard
npm run build     # bundle src + lib0 -> dist/index.mjs (commit this; the Action runs it)

dist/index.mjs is committed on purpose: GitHub does not npm install for JavaScript actions, so the bundle is what runs. Rebuild it whenever you change src/.

Licensing

  • This tool (the code) is MIT licensed — see LICENSE.
  • The agreement texts CLA.md / CLA-ENTITY.md are the Harmony agreements (v1.0), reproduced under CC BY 3.0 with the standard Option Five selection and project placeholders filled in.
  • Nothing here is legal advice. If you adapt the CLA for your own project, consider a quick review by a lawyer — especially the governing-law jurisdiction.

Credits

Inspired by CLA Assistant Lite and the original CLA Assistant. CLA text by the Harmony Agreements project.

About

GitHub Actions CLA Assistant

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors