Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .cursor/rules/git-commits.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
description: Agent commits and PRs - plain git commit with hooks installed, no Cursor attribution
alwaysApply: true
---

# Git Commits

Commit messages and pull requests contain only what the user asked for. Never add
`Co-authored-by:` trailers or any other agent attribution.

Cursor appends a `Co-authored-by: Cursor` trailer to every agent `git commit`, whatever the
settings or rules say. The `strip-cursor-coauthor` commit-msg hook removes it before the
commit is created, so commit normally and let the hooks run.

## Committing

1. Once per checkout, make sure the commit-msg hook is installed. A plain install covers
every stage listed in `default_install_hook_types`:

```bash
test -x "$(git rev-parse --git-path hooks)/commit-msg" || uv run pre-commit install
```

2. Commit with `git commit`. Never pass `--no-verify`: it skips the hook that removes the
trailer, along with the message checks.
3. Verify with `git log -1 --format=full`. If the trailer is still there, the hook did not
run: install it (step 1), then `git commit --amend --no-edit` while the commit is unpushed.

## Fallback: no hooks

If installing the hooks is not an option, build the commit with `git commit-tree`, which
Cursor leaves alone. It skips every hook too, so run them yourself first:

```bash
uv run pre-commit run --files $(git diff --cached --name-only)
uv run pre-commit run --hook-stage commit-msg --commit-msg-filename /tmp/commit-msg
old=$(git rev-parse HEAD)
new=$(git commit-tree "$(git write-tree)" -p "$old" < /tmp/commit-msg)
git update-ref "refs/heads/$(git branch --show-current)" "$new" "$old"
```

Passing the old tip makes `update-ref` fail if the branch moved in the meantime.

## Pull requests

- Never append `Made with Cursor`, `Made with [Cursor](https://cursor.com)`, or similar
attribution to PR titles or bodies.
- PR descriptions contain only **Summary** and **Test plan**, or what the user asked for.
- After `gh pr create` or `gh pr edit`, check `gh pr view <number> --json body` and remove
any injected footer.
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ repos:
# made without hooks (git commit-tree, --no-verify).
- repo: local
hooks:
# Runs first so later commit-msg checks never see the injected trailer.
- id: strip-cursor-coauthor
name: strip Cursor co-author trailer
entry: tools/shell/strip-cursor-coauthor.sh
language: script
stages: [commit-msg]
pass_filenames: true
- id: jargon-commit-msg
name: jargon (commit message)
entry: node --disable-warning=ExperimentalWarning tools/typescript/essentials-sync/src/jargon-check.ts --message-file
Expand Down
33 changes: 33 additions & 0 deletions tools/shell/strip-cursor-coauthor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Remove the `Co-authored-by: Cursor` trailer from a commit message.
#
# Cursor appends the trailer while composing the message, before git runs any
# hook. `--no-verify` does not suppress it and `git commit --amend` re-adds it,
# so a commit-msg hook is the only place that reliably removes it without
# rewriting the commit afterwards. The IDE toggle (Cursor Settings -> Git &
# Pull Requests -> Attribution) covers the local case; this hook also covers
# contributors who have not set it and surfaces that Cloud Agents ignore it.
#
# Matches the agent's mailbox rather than its display name: a human
# contributor could be called "Cursor", but only the bot commits from this
# address. Human `Co-authored-by:` lines are left untouched.

set -euo pipefail

readonly CURSOR_TRAILER='^Co-authored-by:.*<cursoragent@cursor\.com>'

msg_file="${1:?usage: strip-cursor-coauthor.sh <commit-msg-file>}"

if ! grep -qiE "$CURSOR_TRAILER" "$msg_file"; then
exit 0
fi

stripped="$(mktemp)"
trap 'rm -f "$stripped"' EXIT

# `|| true`: grep exits 1 when it filters out every line, which `set -e`
# would otherwise treat as a failure.
grep -viE "$CURSOR_TRAILER" "$msg_file" >"$stripped" || true

# The command substitution drops the blank line the trailer left behind.
printf '%s\n' "$(cat "$stripped")" >"$msg_file"
Loading