Repository navigation
Add a DCO signoff check to CI and the gate - #5055
Conversation
|
The probot DCO app never ran on this repo, so nothing failed an unsigned commit. Replace it with a check we own: - scripts/verify-dco.mjs (+ tests): every commit in <base>..<head> needs a Signed-off-by trailer matching its author or committer; merge and bot-authored commits are exempt. A failure names each commit and the `git rebase --signoff` repair. - .github/workflows/dco.yml: runs it on pull requests over base.sha..head.sha, contents: read only, with a timeout. - `verify:dco` joins local:gate:stages, over origin/v2/main..HEAD. - AGENTS.md states the signoff rule; pr-flow step 3 describes the job instead of the app; quality-gate.md and pre-push-gate cover the stage; the skills-index table is Prettier-clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
e5c04a4 to
45d059d
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The validator accepts matching signoff text outside the trailer block, allowing unsigned commits to pass.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Replaces the inactive DCO app with a repository-owned signoff check for CI and the local pre-push gate.
Changes:
- Adds commit validation, exemptions, repair guidance, and tests.
- Integrates the check into CI and gate stages.
- Updates contributor rules and troubleshooting documentation.
| File | Description |
|---|---|
scripts/verify-dco.test.mjs |
Tests validation, exemptions, and repair behavior. |
scripts/verify-dco.mjs |
Implements commit signoff validation. |
scripts/lib/workflow-gate.test.mjs |
Requires the DCO gate stage. |
package.json |
Registers and integrates verify:dco. |
docs/quality-gate.md |
Documents the stage and renumbers existing stages. |
AGENTS.md |
Adds signoff rules and reformats documentation. |
.github/workflows/dco.yml |
Adds pull-request signoff checks outside main. |
.claude/skills/pre-push-gate/SKILL.md |
Adds signoff failure troubleshooting. |
.claude/skills/pr-flow/SKILL.md |
Replaces app guidance with checker and repair instructions. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
…sage Copilot review on #5055: scanning every line of the message accepted a `Signed-off-by:` quoted in the body, or written as the subject, so an unsigned commit could pass. Read the signoffs with `%(trailers:key=Signed-off-by,valueonly,unfold)`, git's own trailer parser (the one `git commit -s` writes for), and add a regression test for both cases plus a folded trailer that must still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot round 1: 1 finding, fixed.
No suppressed comments. Requesting round 2. |
|
Copilot round 2: 1 finding, declined as out of scope. Round 1's finding shows as resolved.
Nothing was pushed this round, so per |

Closes #4919
Description
The probot DCO app never ran on this repo, so nothing failed a PR with an unsigned commit. This replaces it with a check we own, run in two places by one script:
scripts/verify-dco.mjs(+verify-dco.test.mjs): every commit in<base>..<head>must carry aSigned-off-by: Name <email>trailer whose name and email match the commit's author or committer (name exact, email case-insensitive). The app's two exemptions are kept: merge commits, and bot-authored commits (author email[<id>+]<name>[bot]@users.noreply.github.com). One unsigned commit fails the run; the report names each commit and prints the repair,git rebase --signoff <merge-base>+git push --force-with-lease..github/workflows/dco.yml, job DCO signoff: onpull_request(opened,synchronize,reopened,edited, so a retargeted stacked PR is re-checked), overbase.sha..head.sha, withfetch-depth: 0.permissions: contents: read, no secrets,persist-credentials: false, so it holds no credential and stays outsideverify:action-pins.timeout-minutes: 5. Nonpm ci: the script uses only Node built-ins and git. PRs intomainare skipped: the only one is the milestone merge, whose commits were each checked on the way intov2/mainand whose range reaches back before DCO was adopted.verify:dcojoinslocal:gate:stagesas stage 2 (right afterverify:install-fresh), overorigin/v2/main..HEAD, so an unsigned commit is caught before it is pushed.workflow-gate.test.mjs's stage list now names it, and its CI-parity test seesdco.ymlrunnpm run verify:dco.AGENTS.mdstates the signoff rule (Contributing) and lists the stage (Before pushing);pr-flowstep 3 describes the job instead of the app (the "not installed yet" paragraph and thedco.yml/ remediation-commit / override-button discussion are gone);docs/quality-gate.mdadds the stage and renumbers;pre-push-gategets averify:dcodiagnosis entry. TheAGENTS.mdskills-index table is now Prettier-clean.Server Details
AGENTS.md,docs/quality-gate.md, thepr-flowandpre-push-gateskillsMotivation and Context
#4867 merged with its first acceptance criterion unmet: no DCO check ran on #4904, #4905 or #4906, because the app the plan relied on is off for the org (the Inspector lost its check the same way, modelcontextprotocol/inspector#2566). See #4919.
How Has This Been Tested?
No client-observable surface, so targeted probes:
1. Locally, an unsigned commit fails and names the repair (a scratch
git commit --allow-empty -m "scratch unsigned"on this branch, then reset):Without it:
verify:dco: 1 commit in origin/v2/main..HEAD, all signed off or exempt.(exit 0).2. No false positives on real history. Over recent
v2/main, merges and bot commits included:(Reaching further back, it fails exactly on commits that predate the DCO adoption, e.g.
f46d9578"fix(everything): clean up subscriptions on session disconnect (#4712)", as it should.)3. Unit tests:
scripts/verify-dco.test.mjs, 17 tests: the trailer parser, author/committer matching (name and email both, email case-insensitive), the merge and bot exemptions (and that a[bot]name with a human email is not exempt), the log parser, the arguments, and end to end against throwaway repos: signed passes, unsigned fails naming the commit, the printedgit rebase --signoffrepair, run as printed, makes it pass, a merge in the range is exempt, a missing base says how to fetch it.4.
npm run local:gate: EXIT=0, withverify:dcoas stage 2 andtest:scriptsat 523 tests, 0 failures.5. CI, on this PR: the issue's verification step, run on this PR's branch:
418388b8d4108cebprobe: deliberately unsigned, no-se5c04a40git rebase --signoff 953290e3ad98+git push --force-with-lease, as the failure printedThe failing job's output:
The (empty) probe commit was then dropped, so the branch is the one signed commit.
Breaking Changes
None for users. Contributors: a PR with an unsigned commit now gets a red DCO signoff check, and
npm run local:gatefails on one before the push.Types of changes
Checklist
AGENTS.mdanddocs/quality-gate.mdare updated)npm run changeset) if this changes what a TypeScript server publishes (not applicable: nothing published changes)git logeach fail with a message; bad arguments exit 2)--base/--head, in the script header,docs/quality-gate.mdandpre-push-gate)Additional context
v2/mainneeds a repo-admin ruleset change (no ruleset coversv2/maintoday). Once this merges, a maintainer adds a ruleset targetingv2/mainthat requires theDCO signoffcheck. Until then it reports but does not block.mainis deliberately not covered (see above).🤖 Generated with Claude Code