|
| 1 | +--- |
| 2 | +date: |
| 3 | + created: 2026-11-10 |
| 4 | +readtime: 6 |
| 5 | +slug: ai-attribution-checks-in-ci |
| 6 | +description: >- |
| 7 | + I read the CI checks that eleven projects run on AI-assisted commits. The |
| 8 | + same trailer is required by one project and blocks the merge in another. |
| 9 | + Here is what each one checks, and what none of them can. |
| 10 | +categories: |
| 11 | + - UPDATES |
| 12 | +tags: |
| 13 | + - commit-check |
| 14 | + - ai |
| 15 | + - governance |
| 16 | +authors: |
| 17 | + - shenxianpeng |
| 18 | +--- |
| 19 | + |
| 20 | +# How eleven projects check AI attribution in CI |
| 21 | + |
| 22 | +GitHub's [Octoverse report][octoverse] has this line: |
| 23 | + |
| 24 | +> By September 2026, more than 40% of pull requests on GitHub started with an |
| 25 | +> agent's first draft, up from 17% in March. |
| 26 | +
|
| 27 | +Many of those agents sign their work. Claude Code, for one, adds a |
| 28 | +`Co-Authored-By:` line to its commits unless you turn that off. |
| 29 | + |
| 30 | +Projects have answered with AI policies. A policy file doesn't stop a line |
| 31 | +that a tool adds by default, so some projects now check the rule in CI. I read |
| 32 | +eleven of those checks. Every project name below links to the file at the |
| 33 | +commit I read. |
| 34 | + |
| 35 | +<!-- more --> |
| 36 | + |
| 37 | +## The same two lines, opposite results |
| 38 | + |
| 39 | +Take two lines an agent might leave on a commit, and ask what each project's |
| 40 | +check does with them: |
| 41 | + |
| 42 | +| Project | `Co-Authored-By: Claude …` | `Assisted-by: Claude Code` | |
| 43 | +|---|---|---| |
| 44 | +| [Incus][incus] | fails | fails | |
| 45 | +| [Apache XTable][xtable] | fails | not checked | |
| 46 | +| [JUnit][junit] | fails | fails | |
| 47 | +| [Neovim][neovim] | fails | fails: only a bare `AI-assisted` is allowed | |
| 48 | +| [FreeBSD atf][atf] | fails | passes: it is the trailer they ask for | |
| 49 | +| [Nextcloud][nextcloud] | labels the pull request | labels the pull request | |
| 50 | +| [Vim][vim] | not checked | labels the pull request | |
| 51 | +| [Ant Design Vue][antdv] | passes: it is required | fails: no AI co-author | |
| 52 | + |
| 53 | +Three things stand out. |
| 54 | + |
| 55 | +**The same line is required in one project and blocks the merge in another.** |
| 56 | +Ant Design Vue fails a commit that has no AI co-author: "Every commit must |
| 57 | +include an AI co-author trailer." Incus and JUnit fail a commit that has one, |
| 58 | +and XTable fails a commit that credits Claude. |
| 59 | + |
| 60 | +**Projects that want disclosure don't agree on its form.** FreeBSD atf and |
| 61 | +Nextcloud ask for an `Assisted-by:` trailer, and Nextcloud's example names |
| 62 | +both the tool and the model. Neovim wants the tool left out. Its check prints |
| 63 | +`Only a generic "AI-assisted" token is allowed`, and a trailer that names the |
| 64 | +tool fails. |
| 65 | + |
| 66 | +**Not every check blocks.** Vim and Nextcloud add an "AI assisted" label and |
| 67 | +leave the decision to reviewers. Nextcloud fails only when an agent signs |
| 68 | +off, because the DCO "must only be attested by a human contributor". |
| 69 | + |
| 70 | +So there is no habit a contributor, or an agent, can learn once and use |
| 71 | +everywhere. The rule has to come from the repository they are contributing to. |
| 72 | + |
| 73 | +## Two projects check the pull request instead |
| 74 | + |
| 75 | +[LangChain's docs repository][langchain] reads the pull request description, |
| 76 | +because a squash merge makes it the commit message on main. It fails on the |
| 77 | +"Generated with Claude Code" badge and on a Claude co-author line, and asks |
| 78 | +for "one plain sentence instead, for example: Prepared with Claude Code." |
| 79 | + |
| 80 | +[Solana web3.js][solana] makes the author tick exactly one of two boxes in |
| 81 | +the description: "No AI tooling was used" or "AI tooling was used". The second |
| 82 | +box also needs a filled-in "Tool and extent:" line. For outside contributors |
| 83 | +the check also looks for a tool's signature left in the title, the |
| 84 | +description, the commits or the branch name, and says what it takes that to |
| 85 | +mean: "the change was opened without its author reading it." |
| 86 | + |
| 87 | +## Eight shell scripts and three config files |
| 88 | + |
| 89 | +Eight of the eleven checks are shell scripts the project wrote itself. Most |
| 90 | +carry their own list of tools: XTable's has one name, Incus's nine, |
| 91 | +Nextcloud's fourteen. A list like that is out of date when the next agent |
| 92 | +ships. And a pattern loose enough to find `claude` on an author line also |
| 93 | +stops a contributor whose name is Claude. |
| 94 | + |
| 95 | +The other three use [Commit Check](https://github.com/commit-check/commit-check), |
| 96 | +which I maintain. There the policy is one setting, and the list of tools comes |
| 97 | +with the tool. |
| 98 | + |
| 99 | +[JUnit][junit] forbids AI marks. Its contributing guide says "Git commits must |
| 100 | +be authored by you and not attribute an AI tool as co-author", and the |
| 101 | +workflow that checks it has four settings: |
| 102 | + |
| 103 | +```yaml |
| 104 | +- uses: commit-check/commit-check-action@08357050a644b85d321164f23054f176c13bb018 # v2.20.2 |
| 105 | + env: |
| 106 | + CCHK_AI_ATTRIBUTION: forbid |
| 107 | + CCHK_CONVENTIONAL_COMMITS: false |
| 108 | + # Disable default length rules |
| 109 | + CCHK_SUBJECT_MIN_LENGTH: 0 |
| 110 | + CCHK_SUBJECT_MAX_LENGTH: 10000 |
| 111 | + with: |
| 112 | + message: true |
| 113 | + branch: false |
| 114 | +``` |
| 115 | +
|
| 116 | +[FreeBSD atf][atf] asks for disclosure. The same file also requires a |
| 117 | +sign-off and refuses GitHub's noreply author addresses. This is its AI part: |
| 118 | +
|
| 119 | +```toml |
| 120 | +[commit] |
| 121 | +ai_attribution = "disclose" |
| 122 | +ai_disclosure_trailers = ["Assisted-by"] |
| 123 | +``` |
| 124 | + |
| 125 | +[Evolution Gaming][evolution] is a company, and its policy opens with "AI |
| 126 | +tools may assist with development, but a human author takes responsibility |
| 127 | +for every commit." It publishes one workflow for its repositories to copy |
| 128 | +and mark as required. The workflow sets `CCHK_AI_ATTRIBUTION: "forbid"` and |
| 129 | +keeps a grep of its own next to it, for trailers the version it pins doesn't |
| 130 | +catch. |
| 131 | + |
| 132 | +One commit message gets opposite answers from two of them. In a directory |
| 133 | +with FreeBSD atf's `commit-check.toml`: |
| 134 | + |
| 135 | +```console |
| 136 | +$ cat message.txt |
| 137 | +Fix flaky retry test |
| 138 | + |
| 139 | +Wait for the worker to drain before asserting. |
| 140 | + |
| 141 | +Assisted-by: Claude Code |
| 142 | +Signed-off-by: Jane Doe <jane@example.org> |
| 143 | +$ commit-check --message message.txt |
| 144 | +$ echo $? |
| 145 | +0 |
| 146 | +``` |
| 147 | + |
| 148 | +With JUnit's settings, in a directory with no config file: |
| 149 | + |
| 150 | +```console |
| 151 | +$ export CCHK_AI_ATTRIBUTION=forbid CCHK_CONVENTIONAL_COMMITS=false |
| 152 | +$ export CCHK_SUBJECT_MIN_LENGTH=0 CCHK_SUBJECT_MAX_LENGTH=10000 |
| 153 | +$ commit-check --message message.txt |
| 154 | +✖ Commit rejected by Commit Check · 1 check failed |
| 155 | + |
| 156 | +✖ CC013 ai-attribution failed: Claude Code |
| 157 | + AI attribution is forbidden in this project — detected: Claude Code |
| 158 | + Suggest: This project does not accept AI attribution in commit messages. Remove the AI trailer lines and re-commit. |
| 159 | + Docs: https://commit-check.com/rules/#cc013 |
| 160 | +``` |
| 161 | + |
| 162 | +## What none of them can check |
| 163 | + |
| 164 | +All eleven read what a commit or a pull request says. None can tell who, or |
| 165 | +what, wrote the diff. Delete the trailer and the commit looks like any other. |
| 166 | + |
| 167 | +GitHub's 40% has the same limit. Octoverse defines an agent-started pull |
| 168 | +request as one "whose first draft came from a coding agent that we can |
| 169 | +detect", and says of its agent signals: "It is a floor, since not all agent |
| 170 | +use may be detected." |
| 171 | + |
| 172 | +So these checks don't catch someone who hides AI use. They catch the default: |
| 173 | +the line a tool added and the contributor never looked at. They also tell the |
| 174 | +contributor what this project wants while the pull request is still open, |
| 175 | +which a policy file can't do if nobody opened it. |
| 176 | + |
| 177 | +## If you are writing this rule |
| 178 | + |
| 179 | +- **Decide what a commit should say, not which tools are allowed.** Ten of the |
| 180 | + eleven take one of three positions: no AI marks (JUnit, Incus), disclose it |
| 181 | + in the form the project names (FreeBSD atf, Neovim), or label it and let |
| 182 | + reviewers decide (Vim). Ant Design Vue takes a fourth: always credit the |
| 183 | + tool. |
| 184 | +- **Put the fix in the failure message.** Nextcloud's prints the trailer to |
| 185 | + use in place of the one it rejected. |
| 186 | +- **If you squash on merge, check the description too**, as LangChain does. |
| 187 | + |
| 188 | +With Commit Check the first two positions are `ai_attribution = "forbid"` and |
| 189 | +`ai_attribution = "disclose"`. The |
| 190 | +[policies guide](../../guides/policies.md#recipes-from-real-projects) has the |
| 191 | +settings for more policies, Nextcloud's among them, and |
| 192 | +[the same check for a pull request description](../../guides/policies.md#checking-a-pull-request-description). |
| 193 | +The [GitHub App](https://github.com/apps/commit-check) runs the same rules |
| 194 | +without a workflow. |
| 195 | + |
| 196 | +If your project checks this in a way I didn't cover, I would like to read it. |
| 197 | + |
| 198 | +[octoverse]: https://github.blog/news-insights/octoverse/octoverse-commits-pushes-reach-new-highs-in-2026-as-developers-build-with-agents/ |
| 199 | +[incus]: https://github.com/lxc/incus/blob/63377d9bacba35715edf1bcfb4fd155485a6a6fb/.github/workflows/commits.yml |
| 200 | +[xtable]: https://github.com/apache/incubator-xtable/blob/0e97d0ae94ac81f9b3e2cc3f86cc2d479e67173c/.github/workflows/commit_coauthor_validation.yml |
| 201 | +[junit]: https://github.com/junit-team/junit-framework/blob/13e10545e89a8e217c9d9d2807c449f296205033/.github/workflows/_forbid-ai-attribution.yml |
| 202 | +[neovim]: https://github.com/neovim/neovim/blob/7e394dbb896eaf86cb1046666dd423d512cda55f/.github/workflows/labeler_pr.yml |
| 203 | +[atf]: https://github.com/freebsd/atf/blob/1f183309cb6f7f6352bed578c0d1554812afdcfa/commit-check.toml |
| 204 | +[nextcloud]: https://github.com/nextcloud/android/blob/23951692e38cad0518a000b8542871cfd739e060/.github/workflows/ai-policy.yml |
| 205 | +[vim]: https://github.com/vim/vim/blob/437b1deed5d7aece25ae4a5c1c11c550aa1fe206/.github/workflows/label.yml |
| 206 | +[antdv]: https://github.com/vueComponent/ant-design-vue/blob/7483836f0adac76516e527df893c3d84a7cd4005/.github/workflows/ai-co-author.yml |
| 207 | +[langchain]: https://github.com/langchain-ai/docs/blob/c219b081f6d95d741b99fb6dd69d5a60325e5706/.github/workflows/check-pr-ai-attribution.yml |
| 208 | +[solana]: https://github.com/solana-foundation/solana-web3.js/blob/a325305c5680184a1dce27ddb592188810175883/.github/workflows/pr-hygiene.yml |
| 209 | +[evolution]: https://github.com/evolution-gaming/.github/blob/9486d3e8e67c3fdcf5cb4301ca3a6cfe93072afb/.github/workflows/human-commit-ownership.yml |
0 commit comments