Skip to content

Add a DCO signoff check to CI and the gate - #5055

Merged
cliffhall merged 2 commits into
v2/mainfrom
v2/chore/4919-dco-check
Oct 5, 2026
Merged

cliffhall merged 2 commits into
v2/mainfrom
v2/chore/4919-dco-check

Conversation

@cliffhall

@cliffhall cliffhall commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

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 a Signed-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: on pull_request (opened, synchronize, reopened, edited, so a retargeted stacked PR is re-checked), over base.sha..head.sha, with fetch-depth: 0. permissions: contents: read, no secrets, persist-credentials: false, so it holds no credential and stays outside verify:action-pins. timeout-minutes: 5. No npm ci: the script uses only Node built-ins and git. PRs into main are skipped: the only one is the milestone merge, whose commits were each checked on the way into v2/main and whose range reaches back before DCO was adopted.
  • verify:dco joins local:gate:stages as stage 2 (right after verify:install-fresh), over origin/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 sees dco.yml run npm run verify:dco.
  • Docs: AGENTS.md states the signoff rule (Contributing) and lists the stage (Before pushing); pr-flow step 3 describes the job instead of the app (the "not installed yet" paragraph and the dco.yml / remediation-commit / override-button discussion are gone); docs/quality-gate.md adds the stage and renumbers; pre-push-gate gets a verify:dco diagnosis entry. The AGENTS.md skills-index table is now Prettier-clean.

Server Details

  • Server: none (repository-wide)
  • Changes to: CI workflow, a root guard script and its tests, the pre-push gate, AGENTS.md, docs/quality-gate.md, the pr-flow and pre-push-gate skills

Motivation 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):

$ npm run -s verify:dco; echo EXIT=$?
verify:dco: 1 commit without a valid DCO signoff:
  4d07d39b1884 "scratch unsigned" has no Signed-off-by trailer

Every commit needs a `Signed-off-by: Name <email>` trailer matching its
author or committer (commit with `git commit -s`). To sign off the
commits already made, rewrite them and force-push:

  git rebase --signoff 953290e3ad98
  git push --force-with-lease

`--signoff` signs each commit as you (git config user.name/user.email).
EXIT=1

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:

$ node scripts/verify-dco.mjs --base origin/v2/main~40 --head origin/v2/main
verify:dco: 179 commits in origin/v2/main~40..origin/v2/main, all signed off or exempt.

(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 printed git rebase --signoff repair, 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, with verify:dco as stage 2 and test:scripts at 523 tests, 0 failures.

5. CI, on this PR: the issue's verification step, run on this PR's branch:

Head What was pushed DCO signoff
418388b8 the signed commit ✅ success
d4108ceb + probe: deliberately unsigned, no -s ❌ failure
e5c04a40 after git rebase --signoff 953290e3ad98 + git push --force-with-lease, as the failure printed ✅ success

The failing job's output:

verify:dco: 1 commit without a valid DCO signoff:
  d4108cebc74d "probe: deliberately unsigned (#4919 verification)" has no Signed-off-by trailer

Every commit needs a `Signed-off-by: Name <email>` trailer matching its
author or committer (commit with `git commit -s`). To sign off the
commits already made, rewrite them and force-push:

  git rebase --signoff 953290e3ad98
  git push --force-with-lease

`--signoff` signs each commit as you (git config user.name/user.email).

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:gate fails on one before the push.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follow MCP security best practices (the job is read-only and holds no credential)
  • I have updated the server's README accordingly (not applicable: no server changed; AGENTS.md and docs/quality-gate.md are updated)
  • I have added a changeset (npm run changeset) if this changes what a TypeScript server publishes (not applicable: nothing published changes)
  • I have tested this with an LLM client (not applicable: CI and tooling only, no client-observable surface)
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (an unresolvable base, unrelated histories and a failed git log each fail with a message; bad arguments exit 2)
  • I have documented all environment variables and configuration options (--base / --head, in the script header, docs/quality-gate.md and pre-push-gate)

Additional context

  • Acceptance criterion 3 is not something a PR can do. Making DCO signoff a required status check on v2/main needs a repo-admin ruleset change (no ruleset covers v2/main today). Once this merges, a maintainer adds a ruleset targeting v2/main that requires the DCO signoff check. Until then it reports but does not block. main is deliberately not covered (see above).
  • The bot exemption trusts the author email, and the trailer is a statement anyone can type. The app this replaces checked no more than that; the script header says so.

🤖 Generated with Claude Code

@cliffhall cliffhall added the v2 label Oct 5, 2026
@changeset-bot

changeset-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 988aa91

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cliffhall cliffhall linked an issue Oct 5, 2026 that may be closed by this pull request
5 tasks
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>
@cliffhall
cliffhall force-pushed the v2/chore/4919-dco-check branch 2 times, most recently from e5c04a4 to 45d059d Compare October 5, 2026 05:29
@cliffhall
cliffhall requested a balanced review from Copilot October 5, 2026 05:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 High severity

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.

Comment thread scripts/verify-dco.mjs Outdated
…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>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: 1 finding, fixed.

  • Signoff text outside the trailer block was accepted (high): fixed in 988aa91. Signoffs are now read via git's trailer parser (%(trailers:key=Signed-off-by,valueonly,unfold)) rather than by scanning every line, with a regression test for a body example, a subject-only signoff, and a folded trailer that must still pass. npm run local:gate EXIT=0 (524 script tests); v2/main~40..v2/main (179 commits) still passes.

No suppressed comments. Requesting round 2.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The PR controls the verifier that checks it, allowing unsigned commits to receive a successful DCO result.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread .github/workflows/dco.yml
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 2: 1 finding, declined as out of scope. Round 1's finding shows as resolved.

  • The PR controls the verifier that checks it (run the verifier from a trusted base revision): declined, with the reasoning in the thread. Every check in this repo runs the PR's own code and workflow files. A base-revision script under pull_request would not help, because the PR can edit dco.yml too. Add a DCO signoff check to CI and the signoff rule to AGENTS.md #4919 specified a pull_request workflow, and the control here is maintainer review.

Nothing was pushed this round, so per pr-flow step 9 the loop stops here (Out of scope only). CI: 30 checks pass, including DCO signoff on 988aa91; claude is skipped (comment-triggered).

@cliffhall
cliffhall merged commit b5cea9d into v2/main Oct 5, 2026
35 checks passed
@cliffhall
cliffhall deleted the v2/chore/4919-dco-check branch October 5, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a DCO signoff check to CI and the signoff rule to AGENTS.md

2 participants