← Blog

No AI co-authors

The following patch originates in the SwiftTUI project.

From 9c63bbe5dfa7c69ba748d984b118d2b8170f2448 Mon Sep 17 00:00:00 2001
From: adamz <adam@zethrae.us>
Date: Mon, 8 Jun 2026 07:52:28 +0100
Subject: [PATCH] No AI co-authors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

AI co-authors are an (impressive) marketing hook — but provide
no useful commit signal.

It is June 2026. All code written is facilitated by a host of
sophisticated tools. AI agents are the most recent, but not always
the most notable non-human contributor.

AI 'co-authors' have high utility but no accountability. Tagging
them in commits waters down the _accountability_ chain — and at
worst the practice could imply similarly diffused _responsibility_.

SwiftTUI now forbids commits with these AI git co-authors to set
the expectations that:
* AI tools are probably involved in every commit.
* AI can not have responsibility or accountability for the code.
* The author is, as ever, the person — and as much as the act of
  code authorship might change — the social rules for authorship
  are not fundamentally changed by technology with which it is
  performed.

Co-authored-by: The Swift Compiler <no-reply@swift.org>
---
 AGENTS.md                              |   2 +
 Scripts/check_commit_message_policy.sh | 116 +++++++++++++++++++++++++
 docs/DEVELOPMENT.md                    |   3 +
 prek.toml                              |  11 +++
 4 files changed, 132 insertions(+)
 create mode 100755 Scripts/check_commit_message_policy.sh

diff --git a/AGENTS.md b/AGENTS.md
index 3d9481ef..e33348ae 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -124,6 +124,8 @@ Full policy in [docs/PUBLIC-API.md](docs/PUBLIC-API.md#anyview-policy).
   `Synchronization` primitives.
 - **main-thread-usage** — forbids bare `Thread.isMainThread` without
   justification.
+- **no-ai-coauthors** — rejects commit messages with `Co-authored-by:` trailers
+  whose entry matches common AI model names or AI-bot email identities.
 
 ## Tests
 
diff --git a/Scripts/check_commit_message_policy.sh b/Scripts/check_commit_message_policy.sh
new file mode 100755
index 00000000..3b678d37
--- /dev/null
+++ b/Scripts/check_commit_message_policy.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env sh
+
+set -eu
+
+if [ "$#" -ne 1 ]; then
+  >&2 echo "usage: $0 <commit-message-file>"
+  exit 2
+fi
+
+commit_message_file=$1
+
+if [ ! -f "$commit_message_file" ]; then
+  >&2 echo "Commit message file does not exist: $commit_message_file"
+  exit 2
+fi
+
+violations=$(
+  awk '
+    function trim(s) {
+      sub(/^[[:space:]]+/, "", s)
+      sub(/[[:space:]]+$/, "", s)
+      return s
+    }
+
+    function normalize_identity(s) {
+      s = tolower(s)
+      gsub(/[^a-z0-9]+/, " ", s)
+      return trim(s)
+    }
+
+    function blocked_display_name(name, raw) {
+      raw = normalize_identity(name)
+      name = raw
+
+      if (raw == "replit agent") return 1
+      sub(/ (ai|assistant|bot|chatbot)$/, "", name)
+
+      if (name ~ /^(openai )?(chatgpt|chat gpt)( [0-9]+[a-z]?)*$/) return 1
+      if (name ~ /^(openai )?gpt( [0-9]+[a-z]?)*$/) return 1
+      if (name ~ /^(openai )?codex( (cli|code))?$/) return 1
+      if (name ~ /^(openai|anthropic)$/) return 1
+      if (name ~ /^(anthropic )?claude( (sonnet|opus|haiku|code|[0-9]+[a-z]?))*$/) return 1
+      if (name ~ /^(google )?(gemini|bard)( ([0-9]+[a-z]?|flash|pro|ultra))*$/) return 1
+      if (name ~ /^(github )?copilot( (chat|code))?$/) return 1
+      if (name ~ /^perplexity$/) return 1
+      if (name ~ /^(meta )?llama( [0-9]+[a-z]?)?$/) return 1
+      if (name ~ /^(mistral|mixtral)( [0-9]+[a-z]?)?$/) return 1
+      if (name ~ /^deepseek( [a-z0-9]+)*$/) return 1
+      if (name ~ /^qwen( [0-9]+[a-z]?)*$/) return 1
+      if (name ~ /^(xai )?grok( [0-9]+[a-z]?)?$/) return 1
+      if (name ~ /^(meta ai|ollama|cursor|windsurf|devin|amazon q|amazon q developer|q developer)$/) return 1
+      if (name ~ /^(tabnine|cody|sourcegraph cody)$/) return 1
+
+      return 0
+    }
+
+    function extract_email(entry, candidate) {
+      candidate = entry
+      if (match(candidate, /<[^>]+>/)) {
+        candidate = substr(candidate, RSTART + 1, RLENGTH - 2)
+      } else if (match(candidate, /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z0-9.-]+/)) {
+        candidate = substr(candidate, RSTART, RLENGTH)
+      } else {
+        candidate = ""
+      }
+      return tolower(trim(candidate))
+    }
+
+    function blocked_email(email, local, compact) {
+      if (email == "") return 0
+
+      local = email
+      sub(/@.*/, "", local)
+      compact = local
+      gsub(/[^a-z0-9]+/, "", compact)
+
+      if (compact ~ /^(chatgpt|gpt([0-9]+[a-z]?)?|openai|codex)$/) return 1
+      if (compact ~ /^(claude(sonnet|opus|haiku|code|[0-9]+[a-z]?)?|anthropic)$/) return 1
+      if (compact ~ /^(gemini|bard|copilot|githubcopilot)$/) return 1
+      if (compact ~ /^(perplexity|llama([0-9]+[a-z]?)?|mistral|mixtral)$/) return 1
+      if (compact ~ /^(deepseek(r[0-9]+|v[0-9]+)?|qwen([0-9]+[a-z]?)?|grok|xai)$/) return 1
+      if (compact ~ /^(ollama|cursor|windsurf|devin|replitagent)$/) return 1
+      if (compact ~ /^(amazonq|qdeveloper|tabnine|cody|sourcegraphcody)$/) return 1
+
+      return 0
+    }
+
+    /^[[:space:]]*#/ { next }
+
+    {
+      lower = tolower($0)
+      if (lower !~ /^[[:space:]]*co-authored-by[[:space:]]*:/) next
+
+      entry = lower
+      sub(/^[[:space:]]*co-authored-by[[:space:]]*:[[:space:]]*/, "", entry)
+
+      display_name = entry
+      sub(/[[:space:]]*<.*/, "", display_name)
+
+      email = extract_email(entry)
+
+      if (blocked_display_name(display_name) || blocked_email(email)) {
+        printf "%d: %s\n", NR, $0
+      }
+    }
+  ' "$commit_message_file"
+)
+
+if [ -n "$violations" ]; then
+  >&2 echo "AI model identities must not be added as Co-authored-by trailers."
+  >&2 echo "Remove the AI Co-authored-by entry from the commit message."
+  >&2 echo ""
+  >&2 echo "Offending trailer lines:"
+  >&2 printf '%s\n' "$violations"
+  exit 1
+fi
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
index 64fa46cd..1d594c17 100644
--- a/docs/DEVELOPMENT.md
+++ b/docs/DEVELOPMENT.md
@@ -92,6 +92,9 @@ Hooks run through `prek` (`prek.toml`):
   `SwiftTUICore`, `SwiftTUIViews`, and `SwiftTUI`.
 - `public-surface-policies`, `structured-concurrency-escape-hatches`,
   `main-thread-usage` — the source-policy checks.
+- `no-ai-coauthors` — the commit-message hook rejects `Co-authored-by:`
+  trailers whose entry matches common AI model names or AI-bot email
+  identities.
 
 ## Rendered text fixtures
 
diff --git a/prek.toml b/prek.toml
index 1246b102..75be390a 100644
--- a/prek.toml
+++ b/prek.toml
@@ -2,9 +2,20 @@
 # See https://prek.j178.dev for more information.
 #:schema https://www.schemastore.org/prek.json
 
+default_install_hook_types = ["pre-commit", "commit-msg"]
+
 [[repos]]
 repo = "local"
 hooks = [
+  {
+    id = "no-ai-coauthors",
+    name = "forbid AI model Co-authored-by trailers",
+    entry = "./Scripts/check_commit_message_policy.sh",
+    language = "system",
+    stages = ["commit-msg"],
+    always_run = true,
+    pass_filenames = true
+  },
   {
     id = "swift-format",
     name = "run swift-format format",