Stops commits and pull requests that carry an AI co-author line or a 'Generated with' footer, so your history stays yours.

Keeps AI attribution out of your git history. A git commit whose message carries a Co-Authored-By: line naming an AI (Claude, an Anthropic address, GPT, Copilot, Gemini and so on) is stopped before it runs, and Claude is told to remove the line. A gh pr create or gh pr edit with a "Generated with Claude Code" footer is stopped the same way.
Human co-authors pass untouched.
/config)| Option | Default | |
|---|---|---|
| Allow AI co-author lines | off | Let Co-Authored-By lines that name an AI through |
| Block 'Generated with' footers | on | Also stop commits and pull requests whose message says it was generated with an AI tool |
/plugin install jr-no-ai-trailer --marketplace jrlprost/claude-code-mods
Reads the command of each Bash call. Writes nothing, runs nothing, sends nothing over the network.
hooks/register.ts 14 lines1import type { Register } from 'claude-code'
2
3import { judge } from './trailer'
4
5export const register: Register = (on, options) => {
6 const allowTrailer = options.allowTrailer === true
7 const blockFooter = options.blockFooter !== false
8
9 on('tool.call', { tool: 'Bash' }, ($, e, next) => {
10 const verdict = judge(e.command, { allowTrailer, blockFooter })
11 return verdict ?? next(e)
12 }).catch(($, e, next) => (next.called ? next(e) : { deny: 'jr-no-ai-trailer: the check failed, so the commit was held back.' }))
13}
14hooks/trailer.ts 43 lines1// What counts as an AI attribution in a commit or a pull request.
2
3const AI_NAMES = /(claude|anthropic|openai|chatgpt|gpt-?\d|copilot|gemini|codex|cursor|devin|\bai\b|noreply@anthropic\.com)/i
4
5/** A `Co-Authored-By:` line naming an AI or an AI vendor's address. */
6export const findAiTrailer = (text: string) => {
7 for (const line of text.split(/\\n|\n/)) {
8 const m = line.match(/co-authored-by:\s*(.+)/i)
9 if (m && AI_NAMES.test(m[1] ?? '')) return m[0].trim().replace(/["']+$/, '')
10 }
11 return null
12}
13
14/** The "Generated with Claude Code" footer some tools add to commits and pull requests. */
15export const findGeneratedFooter = (text: string) => {
16 const m = text.match(/(🤖\s*)?generated with \[?(claude code|claude|codex|copilot)[^\n"']*/i)
17 return m ? m[0].trim() : null
18}
19
20export const isCommit = (command: string) => /\bgit\b[^\n;&|]*\bcommit\b/.test(command)
21export const isPullRequest = (command: string) => /\bgh\s+pr\s+(create|edit)\b/.test(command)
22
23export type Verdict = { deny: string } | null
24
25export const judge = (command: string, options: { allowTrailer: boolean; blockFooter: boolean }): Verdict => {
26 const commit = isCommit(command)
27 const pr = isPullRequest(command)
28 if (!commit && !pr) return null
29 if (!options.allowTrailer) {
30 const trailer = findAiTrailer(command)
31 if (trailer) {
32 return { deny: `jr-no-ai-trailer: this ${commit ? 'commit' : 'pull request'} carries an AI co-author line ("${trailer}"). Remove that line and run it again.` }
33 }
34 }
35 if (options.blockFooter) {
36 const footer = findGeneratedFooter(command)
37 if (footer) {
38 return { deny: `jr-no-ai-trailer: this ${commit ? 'commit' : 'pull request'} carries an AI footer ("${footer}"). Remove it and run it again.` }
39 }
40 }
41 return null
42}
43