This document is the authoritative behavior contract for the CLA Bot application. It covers product requirements, page routes, scenario catalog, webhook/PR flows, and test coverage expectations.
- If a contributor has signed a non-current CLA version, they must re-sign before being considered compliant.
- Contributor compliance status is evaluated per org using the contributor's latest signed version for that org.
- Admins can define org-scoped bypass lists for both GitHub users and GitHub Apps/system bots that should always receive a passing CLA check.
- If a contributor has open pull requests and their signature becomes outdated after a CLA update, checks may need to be re-opened/re-evaluated and set to failing until re-signing is completed.
- After a contributor signs/re-signs the latest CLA, the app schedules an async workflow that updates their open PR CLA checks to success and removes stale CLA prompt comments.
- When an org is activated/deactivated, the app schedules an async workflow to re-check open PRs for that org so checks converge to the new enforcement mode.
- GitHub is the user-management source of truth for the app.
- The app has no local signup/password user-management system; DB user rows are GitHub-linked identity mirrors only.
- Authentication/session management is stateless JWT-based (HTTP-only cookie + signed JWT with
jti). - Users can only log in via GitHub OAuth.
- Contributors can view and download every CLA version they have signed.
- Admins can download both current and archived CLA versions for managed orgs.
This section is the behavior contract for UI routes.
| Route | Purpose | Signed-out behavior | Signed-in behavior | Key actions |
|---|---|---|---|---|
/ |
Marketing landing page | Public page | Same | CTA to /auth/signin, example CLA link to /sign/fiveonefour |
/auth/signin |
Start sign-in flow | Shows GitHub sign-in CTA | Same | Sends user to /api/auth/github?returnTo=...; returnTo is sanitized to internal paths only |
/dashboard |
Mode selector page | Public page | Same + session shown in header | Navigate to /admin or /contributor |
/admin |
Org admin overview | Shows "Sign in required" card | Lists organizations user can administer; shows install CTA when none are authorized | Install app (/api/github/install), open org manage pages |
/admin/[orgSlug] |
Org CLA management | If data unavailable, shows "Organization not found" UI | Shows org details, CLA version, signers, archives, bypass list, branch-protection reminder | Edit/save CLA text with live markdown preview modes (Edit, Split, Preview), activate/deactivate bot, copy signing link, inspect signers/archives, manage bypass users and app/bot slugs, download current/archived CLA text, share tab links via ?tab=cla|signers|archives|bypass |
/contributor |
Contributor agreement dashboard | Shows "Sign in required" card | Lists signed CLA history grouped by org status | Re-sign prompts for outdated orgs, links to /sign/[orgSlug], download previously signed CLA records |
/sign/[orgSlug] |
CLA read/sign page | Shows sign-in required (or org not found) | Shows signed state, or sign/re-sign workflow | Requires scroll-to-bottom before sign button enables; handles inactive org warning |
/terms |
Legal terms page | Public page | Same | Documents signing/enforcement terms and branch-protection requirement |
/privacy |
Privacy policy page | Public page | Same | Documents collected data, retention, and rights workflow |
- Users can only authenticate via GitHub OAuth.
- OAuth flow validates
stateand redirects back to a sanitized internalreturnTo. - Session is maintained with an HTTP-only cookie containing a JWT payload (
userId,githubUsername,role,jti). - GitHub remains the source of truth for identity. The DB stores app-side profile/session linkage metadata, not standalone account management.
- If signed out: user sees auth-required state and can start GitHub login.
- If signed in and authorized on at least one installed account: user sees the account list and install button.
- If signed in but authorized on zero installed accounts: user sees install CTA for GitHub App flow.
- Newly installed accounts start with no CLA text. Maintainers must publish their own CLA before external contributors can sign.
- Org manage tabs are URL-synced (
/admin/[orgSlug]?tab=...) so state is shareable and works with browser back/forward navigation.
- User sees signed CLA records including org, version label/hash prefix, and signed timestamp.
- User can view full signing history and download each signed record they own.
- Org compliance status uses the latest signature per org; older historical rows do not keep an org in a warning state once the latest version is signed.
- User can open a CLA from the list and view full language on
/sign/[orgSlug]. - Data detail: full SHA-256 hash is persisted in DB; UI currently shows the short 7-character version label.
- Saving CLA updates current text/hash.
- While editing, maintainers can preview rendering before save (
Edit,Split,Previewmodes; desktop defaults to split view). - Existing signatures remain historical; users on previous hash are treated as outdated and must re-sign.
- If no one has signed prior versions, only current hash/text changes.
- If prior versions were signed, historical signed versions remain in archives/signatures.
- Saving CLA schedules an async workflow recheck of open pull requests; non-member contributors with missing/outdated signatures receive failing checks and updated signing comments once the workflow run completes.
- Org member: check passes, no CLA comment.
- Personal-account repository owner: check passes, no CLA comment.
- User or app/bot on org bypass list: check passes, no CLA comment.
- Non-member + current signature: check passes, no CLA comment.
- Non-member + outdated signature: check fails, re-sign comment posted.
- Non-member + never signed: check fails, sign prompt comment posted.
- Signature is stored with org, user, full CLA hash, accepted hash, assent metadata, immutable GitHub ID at signing time, timestamp, email provenance, and session evidence fields.
- If
repo+pris provided, the signer must match that PR author before targeted PR sync is applied. - After signing/re-signing, the app schedules an async workflow to sync open PRs authored by that contributor in the org: latest CLA check runs are updated to success and stale CLA prompt comments are deleted.
- There is no route to delete signed CLA archives/signature history.
- Signed version history behaves as append-only.
- Contributors can download CLA versions from their own signing history.
- Admins can download both current and archived CLA versions for orgs they administer.
- Download endpoints enforce ownership/authorization and do not expose records across users/orgs.
- When a repository uses GitHub merge queues, the CLA bot auto-passes the check on the merge queue head SHA because CLA compliance was already verified on the original pull request before it entered the queue.
- The bot handles merge queue events via two webhook paths:
check_suitewithaction: "requested"andhead_branchmatchinggh-readonly-queue/prefix: GitHub automatically delivers these to apps withcheckspermission. The bot creates a passing check run on the check suite's head SHA. Non-merge-queue branches and non-requestedactions are ignored.merge_groupwithaction: "checks_requested": handled if the app is subscribed tomerge_groupevents. The bot creates a passing check run on the merge group's head SHA. Non-checks_requestedactions are ignored.
- Missing required payload fields return
400.
- Org deactivated/uninstalled: signing blocked; webhook events set passing CLA checks and remove managed CLA prompts so PRs are not blocked by CLA while inactive.
- Activating or deactivating an org schedules an async open-PR recheck workflow so existing PR checks/comments converge automatically.
- Updating either bypass section (users or app/bots) schedules async open-PR recheck so existing PRs converge to the latest policy.
/recheckauthorization: allowed for PR author, org member, or maintainer; unauthorized users are blocked.- OAuth and install redirects sanitize
returnToto prevent open redirects. - Webhook hardening: production signature verification and delivery de-duplication.
- Standard error paths: unauthorized, forbidden, missing org, invalid payload combinations.
- OAuth callback state mismatch/expired state cookie: sign-in fails safely and redirects back to
/auth/signin?error=.... - GitHub token exchange or profile fetch failure: sign-in fails safely and no session cookie is issued.
- Explicit logout clears JWT cookie; expired/invalid JWT is treated as signed-out.
/recheckis only processed on PR issue comments; non-PR issue comments are ignored.- Non-command comments (or non-created comment events) are ignored.
- If PR head SHA cannot be resolved in production,
/recheckfails with an error instead of guessing.
- Duplicate
x-github-deliveryIDs are ignored via persistent DB-backed delivery tracking to reduce duplicate check/comment churn across process restarts. - Missing/invalid webhook signature is rejected in production when
GITHUB_WEBHOOK_SECRETis configured. - Missing required payload fields return
400and do not mutate DB/check state.
- Installation
createdorunsuspend: account row is created/reactivated, installation ID refreshed, and installation target metadata (organizationvsuser) is persisted. - New installations are initialized with empty CLA text and
cla_text_sha256 = null(no built-in agreement/template is auto-published). - Installation
deletedorsuspend: account is deactivated and installation ID cleared. - Installation repository-change events refresh installation linkage.
- In production, org installs require live GitHub org-admin verification.
- In production, personal-account installs are authorized when the signed-in GitHub user matches the installation target account.
- In local dev/test, org-admin verification is relaxed to keep tests deterministic.
/admin/[orgSlug]and/sign/[orgSlug]handle unknown orgs with explicit not-found states.
- Pull request webhook checks whether PR author is an org member or has signed current CLA.
- PR signature resolution is keyed by immutable GitHub user ID when available (username is fallback only).
- Contributor dashboard status uses the latest stored signature per org to determine current/outdated state in UI.
- Outcomes:
- Org member: passing check, no CLA comment.
- Bypass-listed user/app/bot: passing check, no CLA comment.
- Signed current CLA: passing check, no CLA comment.
- Unsigned/outdated signature: failing check + bot comment with signing URL.
- App/bot bypass matching is slug-based and treats
<slug>and<slug>[bot]as equivalent actor forms. - When CLA text changes, contributors on older signatures are marked as requiring re-sign; open PRs may require check re-evaluation and failure until re-signing.
- After signing/re-signing, an async workflow updates signer-authored open PR CLA checks to success and removes stale CLA prompt comments.
- Activating/deactivating CLA enforcement schedules async open-PR rechecks; inactive mode converges CLA checks to success and clears managed CLA prompt comments.
- CLA bot comment updates/deletions are restricted to CLA-managed comments tagged with an internal signature marker, preventing edits to third-party bot comments.
- Merge queue support: the CLA bot auto-passes checks for merge queue commits. It responds to both
check_suite.requestedevents ongh-readonly-queue/branches (delivered automatically to apps withcheckspermission) andmerge_group.checks_requestedevents (if subscribed). CLA compliance was already enforced on the individual pull request. - Repository maintainers must require
CLA Bot / Contributor License Agreementin GitHub branch protection/rulesets for merge blocking to be enforced. - Markdown ordered lists preserve explicit authored numbering (for example
1.,2.,7.stays1, 2, 7), and legal alpha markers (a./a)) render as ordered sub-clauses with indentation.
Reference UI coverage:
tests/e2e/pages-reference.spec.tshome and dashboard pages render core navigationsign-in page sanitizes external returnToadmin page shows auth-gated state when signed outadmin list and org detail pages render for signed-in admincontributor page shows auth-gated state when signed outcontributor page shows signed agreements when signed insign page handles auth-gated and not-found statessign page supports re-sign flow after CLA update
Reference API/flow coverage:
tests/integration/api-suite.test.ts- Broad integration flow coverage for auth/session APIs, org management, signing/re-signing, webhook checks/comments, install/uninstall/suspend lifecycle, and
/recheckauthorization. - Includes edge cases like stale-signature detection after CLA updates,
/recheckhandling/authorization on open PRs, malformed webhook payload rejection, and duplicate webhook delivery de-duplication.
- Broad integration flow coverage for auth/session APIs, org management, signing/re-signing, webhook checks/comments, install/uninstall/suspend lifecycle, and
When behavior changes:
- Update the relevant sections in this file.
- Update or add coverage in
tests/integration/api-suite.test.tsand/ortests/e2e/pages-reference.spec.tsas appropriate. - Run
pnpm testandpnpm buildbefore merging. Runpnpm test:allwhen UI/browser behavior changes.