npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aggc/no-coauthor

v2.5.0

Published

Git hook that strips AI co-author trailers (Claude, Copilot, Codex, Cursor, Oz/Warp, GPT, Gemini, Devin, etc.) from commit messages while preserving human co-authors

Readme

no-coauthor

CI npm license node

A commit-msg git hook that strips AI Co-Authored-By: trailers from commit messages — while preserving human co-authors and every other trailer (Signed-off-by, Refs, Closes, …).

It runs inside git, not inside the AI tool, so it catches the trailer regardless of which tool added it or whether that tool's own "disable attribution" setting actually works.

Fork of 0xdsgnrd/no-coAuthor — full credit to the original author for the concept and initial implementation. This fork adds email-based detection (not just names), core.hooksPath support, non-destructive install/uninstall, a POSIX shell fallback for machines without Node.js, a user config file, an automated test suite, and cross-platform CI. Published on npm as @aggc/no-coauthor; the installed hook and CLI command are still called no-coauthor.

Quick start

npx @aggc/no-coauthor install

That's it — every commit in this repo now has AI Co-Authored-By: trailers stripped automatically, human co-authors untouched. See Install for the global, curl, and no-Node.js variants.

Table of contents

Why

AI coding assistants (Claude Code, Copilot, Cursor, Codex, Gemini, Oz/Warp, …) inject Co-Authored-By: trailers into commits. Some tools expose a setting to disable this, but those settings are unreliable in practice: Claude Code's attribution setting is intermittently ignored, Cursor has re-enabled it across updates, and tools like Copilot Agent or Gemini Code Assist offer no setting at all.

no-coauthor is a git-level safety net that works regardless of the tool, its version, or its settings. Use it together with your tool's built-in setting when one exists — prevention at the source plus enforcement at the commit boundary. Belt and suspenders.

How it decides what to strip

A Co-Authored-By: Name <email> line is removed when any of these rules match (all matching is case-insensitive and anchored to the trailer line):

| Rule | Condition | Example | |---|---|---| | A. Bot address | The <email> matches a known bot address/domain ([email protected], [email protected], [email protected], [email protected], …). High confidence — no human co-author uses these. | Co-Authored-By: Claude <[email protected]> | | B. Name + bot-shaped email | The name matches a known AI tool name and the email is bot-shaped (noreply, users.noreply.github.com, [bot]). | Co-Authored-By: gemini-code-assist[bot] <176…@users.noreply.github.com> | | C. Name + tool domain | The name matches a known AI tool name and the email domain is a known AI-tool domain (anthropic.com, cursor.com, warp.dev, …). Catches non-noreply bot addresses. | Co-Authored-By: Oz <[email protected]> |

A name match alone never strips a line. This is what keeps a human literally named "Claude", "Cody", or "Devin" intact:

Co-Authored-By: Claude <[email protected]>       → stripped (A, B)
Co-Authored-By: Oz <[email protected]>                → stripped (A, C)
Co-Authored-By: Claude Opus 4.6 <[email protected]> → stripped (A, B — model-suffixed names too)
Co-Authored-By: Claude Smith <[email protected]>       → KEPT (human, unrelated email)
Co-Authored-By: Jane <[email protected]>                → KEPT (same domain, non-AI name)
Co-Authored-By: Pat <[email protected]>                  → KEPT (google.com deliberately excluded, see Limitations)

Everything else in the commit message — Signed-off-by:, Refs:, Closes:, free text, other trailers — is left untouched. Removal is line-based and collapses any blank line left behind so the trailer block stays valid for git's trailer parser.

Separately, a small set of known AI banner/footer lines — plain body lines some tools insert that aren't trailers at all — are stripped the same way. Today this is Claude Code's default commit-body attribution line, which covers the confirmed variants across its CLI versions/docs (emoji prefix optional, link optional, markdown or plain-parenthetical link):

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Generated with [Claude Code](https://claude.ai/code)
🤖 Generated with Claude Code
Generated with Claude Code (https://claude.com/claude-code)

Of the other major tools surveyed (Copilot, Cursor, Codex CLI, Gemini CLI, Aider, Devin, Amazon Q Developer, Windsurf), none currently ship a standardized commit-body banner like this — they either only use a Co-Authored-By: trailer (already covered above) or have no attribution convention at all. Only exact, confirmed formats are matched (see AI_BANNER_LINES in lib/patterns.js), so a commit message that merely mentions a tool by name in its own body text is left untouched.

Covered tools

| Tool | Detected via | |---|---| | Claude / Claude Code (Anthropic) | [email protected], name match | | GitHub Copilot | [email protected], *copilot*@users.noreply.github.com, [bot] forms | | OpenAI Codex | [email protected], [email protected], GitHub noreply form | | ChatGPT / GPT (OpenAI) | @chatgpt.com, name match | | Cursor (incl. Cursor Agent / background agents) | [email protected]/.sh, [email protected] | | Gemini / Bard / Gemini Code Assist (Google) | gemini*@google.com, bard*@google.com, [bot] forms | | Oz (Warp) | [email protected] | | Codeium / Windsurf | [email protected], [email protected] | | Tabnine | [email protected] | | Amazon Q / CodeWhisperer | name match (amazon-q, amazonq, CodeWhisperer) | | Aider | [email protected]/.ai | | Zed AI | [email protected] | | Cody (Sourcegraph) | [email protected] | | Devin (Cognition) | [email protected]/.dev | | Augment Code | [email protected] | | Replit Agent / Ghostwriter | [email protected] | | Cline, Continue, Llama, Tabby, Bolt, v0, Lovable, Goose, OpenHands, Plandex, Qoder, Jules | name match |

Also stripped as a standalone body line (not a trailer): Claude Code's Generated with Claude Code attribution banner, in its confirmed variants (with/without emoji, with/without a link) — see How it decides what to strip and AI_BANNER_LINES in lib/patterns.js.

This list is best-effort and evolves as tools change their default trailers. Missing one? Add it locally via config — no code change needed — or open a PR against lib/patterns.js.

Install

npm (recommended — Node.js hook with config-file support)

# Per-project (inside a git repo)
npx @aggc/no-coauthor install

# Every repo on this machine
npx @aggc/no-coauthor install --global

--global sets git's own core.hooksPath globally, so it affects every repo on the machine that doesn't already set its own local core.hooksPath — not just repos you plan to use it with. If you have older per-project hooks (e.g. a husky v4-style setup that writes straight into .git/hooks instead of setting core.hooksPath), they'll stop running silently. Run npx @aggc/no-coauthor status in any repo you're unsure about to check.

curl (no Node.js required — POSIX shell hook)

# Per-project
curl -fsSL https://raw.githubusercontent.com/jmtrs/no-coAuthor/main/install.sh | sh

# Global
curl -fsSL https://raw.githubusercontent.com/jmtrs/no-coAuthor/main/install.sh | sh -s -- --global

POSIX shell hook via npm

npx @aggc/no-coauthor install --no-node

Homebrew (no Node.js required)

brew install jmtrs/tap/no-coauthor
no-coauthor install          # per-project
no-coauthor install --global # every repo on this machine

Same POSIX shell hook as the curl installer above — jmtrs/homebrew-tap just packages install.sh under the no-coauthor command name. The formula is updated automatically on every release (see .github/workflows/release.yml).

pre-commit framework

Already using pre-commit? Add this repo to your .pre-commit-config.yaml instead of running install — pre-commit manages the commit-msg hook file for you:

repos:
  - repo: https://github.com/jmtrs/no-coAuthor
    rev: v2.4.0 # use the latest tag: https://github.com/jmtrs/no-coAuthor/tags
    hooks:
      - id: no-coauthor

Then:

pre-commit install --hook-type commit-msg

This runs no-coauthor commit-msg <file> under pre-commit's own Node environment instead of through no-coauthor install, so it does not get the wrapper/preserve-foreign-hook handling lib/install.js does for a direct install — pre-commit is already managing the hook file and any other commit-msg hooks you've configured through it. Everything else (which trailers/banners get stripped, .no-coauthorrc.json, NO_COAUTHOR_DISABLE) behaves the same either way.

Uninstall

npx @aggc/no-coauthor uninstall          # per-project
npx @aggc/no-coauthor uninstall --global # global

How install works (non-destructive)

  • No existing commit-msg hook → writes ours (standalone).
  • Existing foreign hook → preserved as commit-msg.orig and replaced with a small shell commit-msg wrapper that runs the previous hook then no-coauthor. Your existing hook keeps working unmodified.
  • Already installed → updated in place (idempotent — safe to re-run).
  • core.hooksPath-aware — if the repo sets a local core.hooksPath (e.g. .githooks), the hook is installed there, not in .git/hooks (which git would otherwise ignore). If a global core.hooksPath would shadow a per-project install, you get a warning with the fix.
  • Worktree- and submodule-safeinstall/uninstall/status resolve the hooks directory through git rev-parse --git-common-dir rather than assuming <repo>/.git is a directory (it's a text file pointing elsewhere in both). Installing from a linked worktree writes to the main repo's shared hooks dir — the same place git itself reads hooks from for every worktree — so one install covers all of them.

Uninstall restores the preserved foreign hook if one exists, otherwise removes the no-coauthor hook cleanly.

Run npx @aggc/no-coauthor status any time to confirm the hook is still there and actually stripping trailers — see CLI reference.

Configuration

Optional .no-coauthorrc.json adds custom patterns on top of the built-ins. Entries are treated as literal strings and escaped automatically, so no regex knowledge is required:

{
  "names": ["MyAgent"],
  "emails": ["[email protected]"],
  "domains": ["myteam.ai"],
  "banners": ["Generated by MyAgent v2"]
}

The Node.js hook and the check command read this at runtime. There are two locations, with different trust levels:

  • ~/.no-coauthorrc.json — your own machine, always honored.
  • ./.no-coauthorrc.json (repo root) — ignored by default. A cloned repo could ship a config that silently strips real human co-author attribution, which is exactly what this tool exists to prevent. To honor a repo-root config you control (e.g. a team-shared one in CI), opt in with:
export NO_COAUTHOR_TRUST_REPO=1

The POSIX fallback hook does not read either file — it is fully self-contained by design, with no filesystem reads beyond the commit message itself.

Temporarily disabling

Two ways to skip stripping, for different scopes:

# Skip every hook for one commit (git's own escape hatch) — also skips any
# OTHER hook this may be wrapping (see [How install works](#how-install-works-non-destructive)).
git commit --no-verify -m "..."

# Skip only no-coauthor for one commit — any wrapped hook still runs.
NO_COAUTHOR_DISABLE=1 git commit -m "..."

# Skip only no-coauthor for a whole shell session.
export NO_COAUTHOR_DISABLE=1

NO_COAUTHOR_DISABLE is checked first thing in both the Node and POSIX hooks, before any matching happens. Prefer it over --no-verify whenever a foreign hook is wrapped and you still want that one to run.

Server-side enforcement

The commit-msg hook is a client-side, opt-in safety net, not a security boundary. Anything running on the committer's own machine — including an AI agent with shell access — can skip it: --no-verify, NO_COAUTHOR_DISABLE=1, or just never running install in the first place. No local git hook can be made bypass-proof against the same machine it runs on; that's true of this tool and of every other commit-msg hook (husky, pre-commit, lint-staged, …) equally, not a no-coauthor-specific weakness.

If your threat model includes an agent that might deliberately re-add attribution rather than just doing it by unreliable default, the hook alone isn't enough — you need a check that runs somewhere the committer doesn't control: CI, as a required PR status check.

npx @aggc/no-coauthor check <range>   # exit 1 if any commit in <range> has an AI trailer
npx @aggc/no-coauthor check           # defaults to HEAD~1..HEAD

check reuses the exact same detection logic as the hooks (same lib/patterns.js, same .no-coauthorrc.json), so it can never flag something the hook wouldn't also have stripped, or vice versa. Copy examples/reject-ai-coauthor.yml into your repo's .github/workflows/ — it computes the commit range from the PR's base/head SHAs, so it works the same regardless of which branch a PR targets or is opened from, not just main.

A failing check does not block a merge by itself — add a branch protection rule (or ruleset) on your target branch(es) requiring that job to pass, and restrict who can bypass required checks. That combination — hook for the common case, required CI check for the case where someone tries to route around it — is what actually can't be defeated from a local shell.

CLI reference

$ npx @aggc/no-coauthor <command> [options]

Commands
  install             Install hook in current repo
  install --global    Install as global git hook
  install --no-node   Install POSIX shell hook (no Node.js needed)
  uninstall           Remove hook from current repo
  uninstall --global  Remove global git hook
  status              Check the hook is installed and actually stripping trailers
  status --global     Check the global hook instead of the local one
  check [range]       Scan already-made commits for AI trailers (default: HEAD~1..HEAD)
                      For CI use — see [Server-side enforcement](#server-side-enforcement)
  commit-msg <file>   Strip trailers/banners from a commit message file in place.
                      Internal: invoked by pre-commit, not meant to be run by hand
                      — see [pre-commit framework](#install)

Options
  --no-node           Use POSIX shell hook instead of Node.js
  -h, --help          Show help
  -v, --version       Show version

status exists because this tool's whole job is to run invisibly on every commit — if something else later overwrites .git/hooks/commit-msg (a hook manager like husky/lefthook reinstalling, a manual edit, a fresh git init template), no-coauthor silently stops working with zero visible signal, which is exactly the failure mode it exists to prevent. status checks the hook file is present, is ours, is executable, and then actually runs it against a synthetic AI trailer to confirm it still strips correctly — not just that a plausible-looking file exists:

$ npx @aggc/no-coauthor status
no-coauthor: checking local hook at /path/to/repo/.git/hooks
✔ commit-msg hook is installed and managed by no-coauthor
✔ /path/to/repo/.git/hooks/commit-msg is executable
✔ live check: a synthetic AI trailer was stripped and a human trailer was kept

Architecture

bin/no-coauthor.js       CLI entry point (install / uninstall / --help / --version)
lib/git-utils.js         Shared git plumbing (gitConfig, gitRoot, gitCommonDir) used
                         by install/uninstall/status to resolve the hooks dir —
                         worktree/submodule-safe (see How install works above)
lib/install.js           Non-destructive install: standalone, wrap, or update-in-place
lib/uninstall.js         Restores a preserved foreign hook, or removes ours cleanly
lib/status.js            Checks the hook is installed, managed, executable, and
                         actually strips a synthetic AI trailer right now
lib/check.js             Server-side companion: scans a git revision range of
                         already-made commits for AI trailers (reuses strip.js)
lib/commit-msg.js        Runtime commit-msg entry point for `no-coauthor commit-msg
                         <file>` — what .pre-commit-hooks.yaml shells out to
                         (reuses strip.js, unlike the generated hooks below)
lib/hook.js              Builds the self-contained Node.js commit-msg hook that gets
                         written to disk (inlines strip.js + patterns.js — no
                         runtime dependency on this package once installed)
lib/hook-posix.js        Builds the POSIX /bin/sh commit-msg hook from the same
                         patterns.js source (JS regex fragments transliterated
                         to POSIX ERE character classes)
lib/patterns.js          Single source of truth for bot emails, tool domains,
                         and AI tool names — both hooks are generated from this
lib/strip.js             Core matching/stripping logic (rules A/B/C), shared by
                         the Node hook builder and the test suite
install.sh               Standalone POSIX installer for the curl one-liner;
                         embeds a copy of the hook-posix.js output
scripts/sync-install-sh.js  Regenerates that embedded copy after a
                         lib/patterns.js change (`npm run sync-install-sh`)
examples/reject-ai-coauthor.yml  Copy-paste GitHub Actions workflow wired to
                         `no-coauthor check` — see Server-side enforcement above
.pre-commit-hooks.yaml   Exposes this repo as a pre-commit `commit-msg`-stage
                         hook — see pre-commit framework under Install above

Both hook implementations are generated from lib/patterns.js, so a tool added there is picked up by the Node hook, the POSIX hook, and (after regeneration) install.sh alike. test/install.test.js asserts the install.sh embedded hook body stays byte-identical to lib/hook-posix.js's output, so the two can't silently drift.

Cross-platform

  • Node.js hook (default) — Linux, macOS, and Windows (Git for Windows / Git Bash).
  • POSIX shell hook (fallback) — anywhere with /bin/sh, grep -E, and awk; no Node.js required.

CI (.github/workflows/ci.yml) runs the full test suite on Ubuntu, macOS, and Windows against Node 18 / 20 / 22, plus a dedicated end-to-end job that installs the POSIX hook with real sh/grep/awk and performs an actual git commit.

Limitations

  • status's live check only exercises the built-in patterns (a synthetic Claude/Anthropic trailer) — it does not know about your .no-coauthorrc.json custom patterns, so a working status doesn't guarantee a misconfigured custom pattern is actually matching.
  • Lines longer than 500 chars are never treated as a Co-Authored-By trailer and are left untouched (no real one gets remotely close — this bound exists to keep the Node hook's regex matching from doing quadratic-time work on an adversarial or buggy-tool-generated giant line).
  • The POSIX fallback is best-effort: it compiles all patterns into one combined ERE and does not read the config file. Prefer the Node hook when possible; use --no-node only when Node.js is unavailable.
  • google.com is intentionally not listed as an AI-tool domain (rule C) — it's too broad and would risk stripping real Google employees. Gemini/Bard bot accounts are still caught by their specific addresses (rule A).
  • A human co-author who shares a tool-company domain and happens to have an AI tool name (e.g. an Anthropic employee literally named "Claude") would be stripped by rule C. This is an accepted, rare trade-off — narrow it with config if it matters to you.
  • Detection is pattern-based against known trailer shapes. A tool that starts emitting a previously-unseen bot address/domain won't be caught until patterns.js is updated (or you add it via config).
  • Banner/footer-line stripping is intentionally narrow: only Claude Code's confirmed default formats are built in today (a survey of Copilot, Cursor, Codex CLI, Gemini CLI, Aider, Devin, Amazon Q Developer, and Windsurf found no other standardized commit-body banner as of this writing — only Co-Authored-By: trailers, already covered above, or no attribution convention at all). Claude Code's own attribution.commit setting also lets a user/team override this text to anything, which by definition no fixed pattern can catch — add a custom banner via banners in config, or open a PR against AI_BANNER_LINES if another tool starts shipping its own standardized one.

Contributing

Bug reports and new tool patterns are welcome.

npm test                  # full suite: strip logic, install/uninstall, POSIX hook, sh -n
npm run sync-install-sh   # regenerate install.sh after touching lib/patterns.js
npx changeset             # record a changelog entry for your change (patch/minor/major + summary)

If you add or change a pattern in lib/patterns.js:

  1. Add a strip.test.js case for both the strip and the preserve side.
  2. Run npm run sync-install-sh so the curl installer matches — CI fails the build otherwise (test/install.test.js checks they're byte-identical).
  3. test/posix-parity.test.js automatically checks your new pattern strips correctly under the POSIX hook too; no extra test needed for that part.
  4. npx changeset before opening the PR — merging to main won't publish anything by itself, but a maintainer merging the resulting "Version Packages" PR will (see Releasing).

Releasing

This repo publishes via Changesets: every push to main with a pending changeset opens/updates a "Version Packages" PR; merging that PR bumps the version, updates CHANGELOG.md, and publishes to npm automatically (.github/workflows/release.yml).

Credits

Originally created by 0xdsgnrd0xdsgnrd/no-coAuthor. This fork builds on that work; see the table of contents above for everything added on top.

License

MIT — see LICENSE.