{"slug": "what-i-learned-counting-ai-co-authored-commits-across-26000-of-my-own", "title": "What I learned counting AI co-authored commits across 26,000 of my own", "summary": "A developer built a tool that counts AI co-authored commits by parsing Co-Authored-By trailers in git history, finding that 10,261 of their 26,251 commits (39%) were co-authored by Claude. The project also uncovered an undocumented GitHub commit search qualifier for co-authored-by and showed that keying agents on trailer display names rather than email addresses produces false matches and miscounts. The developer catalogued the actual trailer addresses used by nine coding agents, including Claude, Cursor, Copilot, Codex, and Devin.", "body_md": "I use Claude Code every day, but if you asked me how much of my code I actually\n\nwrite with it, all I had was a feeling. The data was sitting right there in git\n\nthe whole time:\n\n``` bash\n$ git log --format='%b' | grep -c 'Co-Authored-By: Claude'\n960\n```\n\n960 out of 1,107 commits in that repo. Across everything I've touched:\n\n**10,261 of 26,251 commits — 39%.**\n\nI built [a small tool](https://github.com/yosh1/cocommit) to render that as an\n\nSVG card for a GitHub profile. The tool is the boring part. What I found while\n\nbuilding it is what I actually want to write about.\n\n`co-authored-by:` search qualifier\nThe commit search API accepts this:\n\n``` bash\n$ gh api '/search/commits?q=author:USER+co-authored-by:noreply@anthropic.com&per_page=1' \\\n    --jq '.total_count'\n```\n\nIt works. It is also documented nowhere — it does not appear in [GitHub's\ncommit search docs](https://docs.github.com/en/search-github/searching-commits).\n\nSince \"it seems to work\" is not evidence, I ran a control (counts move as I\n\ncommit, so these are one snapshot rather than fixed values):\n\n| Query (all with `author:USER` ) | `total_count` | \n|---|---|\n| `co-authored-by:noreply@anthropic.com` | 10,261 | \n| `co-authored-by:copilot@github.com` | 29 | \n| `zzznotaqualifier:noreply@anthropic.com` | **0** | \n| no qualifier | 26,251 | \n\nAn invented qualifier returns zero. Change the value and the count changes.\n\nThe parser recognizes it.\n\nBut undocumented means it can vanish without notice, and **the dangerous failure mode isn't returning zero.** If GitHub stops parsing the qualifier, it\n\nSo the check is on both sides:\n\n```\n// Co-authored commits are a strict subset of all commits. A count equal to\n// the total means the qualifier didn't filter at all.\nconst unusable = (n) => n === 0 || n >= total;\n```\n\nThis is the one that surprised me, and it's the bug I shipped first.\n\nMy initial implementation searched `co-authored-by:claude` — the agent's name.\n\nReasonable-looking, and it returns a plausible number. Then I added Cursor and\n\ngot this:\n\n``` bash\n$ gh api '/search/commits?q=co-authored-by:cursor+is:public' --jq '.total_count'\n10033042\n```\n\nTen million felt too round. I looked at what was actually matching:\n\n``` bash\n$ gh api '/search/commits?q=co-authored-by:cursor+is:public&per_page=5' \\\n    --jq '.items[].commit.message' | grep -i co-authored\nCo-authored-by: Cursor <cursoragent@cursor.com>\n```\n\nThe real trailer uses an address. Searching the *name* matches any co-author\n\nwho happens to share it. And when I checked my own supposedly-correct Claude\n\nnumbers:\n\n```\nname form : 10,262\nemail form: 10,255\n```\n\nSeven commits of drift. Looking at what those matched, they carried a second\n\ntrailer naming a human collaborator — a teammate, counted as an AI, because a\n\nname is not an identity. Every agent is now keyed on the address in its\n\ntrailer.\n\nThis matters more than it sounds, because some tools put *variable* text in the\n\ndisplay name:\n\n```\nCo-authored-by: aider (anthropic/claude-sonnet-5) <aider@aider.chat>\nCo-authored-by: aider (ollama/gemma4:e4b-mlx)    <aider@aider.chat>\n```\n\nAider appends whichever model it used. The name is unstable; the address isn't.\n\nI read the trailers off real public commits rather than trusting vendor docs.\n\nHere's what nine agents actually write, with the public commit count each one\n\nhas as of today:\n\n| Agent | Trailer address | Public commits | \n|---|---|---|\n| Claude | `noreply@anthropic.com` | 89,025,757 | \n| Cursor | `cursoragent@cursor.com` | 8,374,584 | \n| Copilot | `copilot@github.com` | 4,046,637 | \n| Codex | `codex@openai.com` ,`noreply@openai.com` | 1,812,065 | \n| Devin | `devin-ai-integration[bot]@users.noreply.github.com` | 588,010 | \n| Gemini | `gemini-code-assist@google.com` + bot address | 290,638 | \n| Jules | `google-labs-jules[bot]@users.noreply.github.com` | 233,948 | \n| Aider | `aider@aider.chat` | 133,041 | \n| Amp | `amp@ampcode.com` | 104,075 | \n\nOne tool I expected to find had no dedicated trailer at all — searching for it\n\nreturned only human contributors. Worth checking before you assume.\n\nEvery image in a README is rewritten through `camo.githubusercontent.com`.\n\nPeople repeat a lot of folklore about what survives that trip, so I read the\n\nresponse headers:\n\n```\ncontent-security-policy: default-src 'none'; img-src data:; style-src 'unsafe-inline'\n```\n\nThat single line settles it:\n\n| JavaScript | **blocked** (`default-src 'none'` ) | \n| Web fonts | **blocked** — system font stacks or nothing | \n| External images | **blocked** (`img-src data:` only) | \n| CSS animation | **allowed** (`style-src 'unsafe-inline'` ) | \n\nAnimation works, which is fun — until it doesn't. My first version grew the\n\nbars from zero:\n\n```\n<rect height=\"0\"><animate attributeName=\"height\" to=\"28\"/></rect>\n```\n\nRasterize that locally and **the bars disappear.** Anything that ignores SMIL\n\nsees `height=\"0\"`. Static renderers, social preview generators, and thumbnail\n\npipelines all land there.\n\nThe fix is to make the resting state the truth and let animation only supply a\n\nstarting point:\n\n```\n<rect height=\"28\"><animate attributeName=\"height\" from=\"0\" to=\"28\"/></rect>\n```\n\nNow it animates where animation runs and looks finished everywhere else. There\n\nis a test for it, because I will absolutely make this mistake again:\n\n``` js\ntest('animated rendering still declares the finished geometry', () => {\n  const svg = renderCard(sample, { animate: true });\n  assert.doesNotMatch(svg, /height=\"0\"/);\n});\n```\n\nI started out planning a github-readme-stats-style URL that returns an SVG.\n\nThen I compared the two numbers I could offer:\n\n| Scope | AI-written | Total | Share | \n|---|---|---|---|\n| including private | 10,261 | 26,251 | **39%** | \n| public only | 126 | 5,688 | **2%** | \n\nWork code lives in private repos. A public-only number isn't a smaller version\n\nof the truth, it's a different number — 2% instead of 39%.\n\nAnd commit search only sees repositories the token owner can reach. Other\n\npeople's *public* commits are searchable, but their private ones never are. So\n\na hosted service can only ever offer the 2% number, no matter how many tokens\n\nit pools.\n\nRunning in the user's own CI solves it completely: their token, their private\n\nrepos, their number. No server, no one else's PAT to hold.\n\nThe API shape helps too — I only ever read `total_count`, never enumerate\n\ncommits. (Which is lucky, since search caps enumeration at 1,000 results while\n\n`total_count` stays accurate well past it.) Repository names, commit messages\n\nand diffs are never read.\n\nTwelve months of history, charted:\n\nOctober 2025: 42 commits. September 2026: 2,067. **A 49x increase**, with the\n\nmost recent months running above 80%.\n\nI knew my workflow had changed. I didn't know it had changed *that* much, and I\n\ncouldn't have told you so with a number before this. That turned out to be\n\nworth more than the card itself.\n\nThe tool is MIT licensed and has zero dependencies:\n\n``` bash\n$ GITHUB_TOKEN=ghp_... npx github:yosh1/cocommit --user your-login\n```\n\n[https://github.com/yosh1/cocommit](https://github.com/yosh1/cocommit)\n\nIf your agent isn't in the table, `git log --format='%b' | grep -i co-authored`\n\nwill show you what it writes — send me the trailer and I'll add it.", "url": "https://wpnews.pro/news/what-i-learned-counting-ai-co-authored-commits-across-26000-of-my-own", "canonical_source": "https://dev.to/yosh1/what-i-learned-counting-ai-co-authored-commits-across-26000-of-my-own-1h9m", "published_at": "2026-09-20 17:40:26+00:00", "updated_at": "2026-09-20 17:54:31.725479+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["GitHub", "Claude", "Anthropic", "Cursor", "GitHub Copilot", "OpenAI Codex", "Devin", "Aider"], "alternates": {"html": "https://wpnews.pro/news/what-i-learned-counting-ai-co-authored-commits-across-26000-of-my-own", "markdown": "https://wpnews.pro/news/what-i-learned-counting-ai-co-authored-commits-across-26000-of-my-own.md", "text": "https://wpnews.pro/news/what-i-learned-counting-ai-co-authored-commits-across-26000-of-my-own.txt", "jsonld": "https://wpnews.pro/news/what-i-learned-counting-ai-co-authored-commits-across-26000-of-my-own.jsonld"}}