If an AI agent writes your commits, it should sign them with its own GPG key, not with yours. A friend who works at a big tech company told me this is now an internal rule there, and after thinking about it for a while, I agree with it.
Most of us barely write commits by hand anymore. Claude Code, Codex and other agents do the typing, and we review. But many people still let the agent sign with their personal key, so every commit looks the same: "signed by me". In this post I explain why that is a problem, and then I show you, step by step, how to give Claude Code and Codex their own keys and register them on GitHub.
Why give each agent its own key?
There are more reasons than the three below, but these are the ones you will run into most often.
1. The agent shouldn't have your private key
This is the most important reason. To sign commits with your key, the agent needs access to it. Agents often run in places that are much less safe than your laptop or your hardware token: a sandbox, a CI runner, a cloud VM.
Agents can also be tricked. A prompt injection hidden in an issue, a README, a dependency or a web page can make an agent do things you never asked for. If that agent holds your key, it can create commits that look exactly like commits you personally signed. Nobody can tell the difference.
With a separate key, the damage is limited. If the agent key leaks, you revoke that one key. Your own key, your identity and your other projects stay safe.
2. Audits and incident response get easier
Sooner or later something goes wrong: a bug in production, a security hole, a strange change nobody remembers making. When that happens, the first question is "where did this come from?"
If each agent signs with its own key, the answer is in git log. You can filter all commits made by Claude Code, or only the ones made by Codex, or only the ones made during the week an agent was acting strangely. With one shared key, all of that history is mixed together, and you have to guess.
This also helps with compliance. More and more companies are being asked how they track AI-generated code. "Every agent commit is signed with a dedicated key" is a simple and clear answer.
3. Signatures are about who vouches for the code, not who typed it
A common reaction is: "Nobody writes code by hand anymore, so why separate anything?" I think it is the opposite. Because almost everything is generated now, the useful question is no longer "who typed this?". It is "did a human look at this and take responsibility for it?"
With separate keys, each signature means something clear:
- Agent key: an agent produced this commit, in a session started by this person.
- Your key: a human reviewed and approved this, for example on the merge or the release.
Your personal key becomes rarer, and that makes it more valuable. It works like a sign-off, not like a record of typing. It also lets you write rules such as "commits signed only by an agent key cannot merge to main without a human approval".
The setup
The plan is simple. Each agent gets:
- Its own GPG keyring, in a separate folder, so the agent's
gpgcan't even see your personal key. - Its own email address and name, so its commits are easy to spot.
- A config that tells git to use that key every time the agent commits.
I use Gmail's plus addressing for the emails (you+claude@gmail.com, you+codex@gmail.com). Messages still arrive in your normal inbox, so you can verify them on GitHub. Replace you, Your Name and /home/YOU with your own values in all the commands below.
Step 1: Create a key for Claude Code
First, create a separate keyring folder. GNUPGHOME tells gpg which folder to use, so this key lives apart from your personal one.
mkdir -p ~/.gnupg-claude && chmod 700 ~/.gnupg-claude
GNUPGHOME=~/.gnupg-claude gpg --batch --pinentry-mode loopback --passphrase '' \
--quick-gen-key "Your Name (Claude Code) <you+claude@gmail.com>" ed25519 sign 1y
A few things to notice:
- No passphrase. The agent can't type one, so the key has none. This is acceptable because the key can only sign, it expires, and you can revoke it without touching your own key.
-
ed25519 signcreates a modern, small key that is only allowed to sign. -
1ymakes the key expire after one year. Rotating agent keys regularly is a good habit.
Now find the key ID. It is the part after ed25519/ in the sec line:
GNUPGHOME=~/.gnupg-claude gpg --list-secret-keys --keyid-format=long
sec ed25519/ABCD1234EF567890 2026-11-09 [SC] [expires: 2027-11-09]
In this example the key ID is ABCD1234EF567890. Keep it, you will need it in the next steps.
Step 2: Register the email and the key on GitHub
GitHub only shows a commit as Verified when two things match: the signature belongs to a GPG key on your account, and the commit email is a verified email on your account. So you need to add both.
Add the email:
- Go to GitHub → Settings → Emails.
- Type
you+claude@gmail.comin Add email address and click Add. - Open the confirmation email (it lands in your normal Gmail inbox) and click the link.
Add the GPG key. First, export the public part of the key:
GNUPGHOME=~/.gnupg-claude gpg --armor --export ABCD1234EF567890
Copy everything from -----BEGIN PGP PUBLIC KEY BLOCK----- to -----END PGP PUBLIC KEY BLOCK-----. Then:
- Go to GitHub → Settings → SSH and GPG keys.
- Click New GPG key.
- Give it a clear title, like
Claude Code (laptop), paste the key and click Add GPG key.
Import the public key on your machine. Your normal keyring (~/.gnupg) has never seen this key. Without this step, git log --show-signature in your own terminal says Can't check signature: No public key, even though the commit is signed correctly. Import only the public part. The private key stays in ~/.gnupg-claude.
GNUPGHOME=~/.gnupg-claude gpg --armor --export ABCD1234EF567890 | gpg --import
Optionally, sign the agent key with your own key. This removes the "key is not certified" warning, and it also records that you authorized this agent:
gpg --list-keys ABCD1234EF567890 # copy the full fingerprint
gpg --quick-sign-key <FINGERPRINT>
Step 3: Tell Claude Code to use its key
Claude Code adds the variables from the env block of its settings file to every command it runs. Git (2.31 or newer) can also read config values from environment variables, through GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n and GIT_CONFIG_VALUE_n. Together, this means you don't need to change your global git config at all.
Add this to ~/.claude/settings.json to apply it everywhere, or to .claude/settings.json inside a repo to apply it only there:
{
"env": {
"GNUPGHOME": "/home/YOU/.gnupg-claude",
"GIT_AUTHOR_NAME": "Your Name (Claude Code)",
"GIT_AUTHOR_EMAIL": "you+claude@gmail.com",
"GIT_COMMITTER_NAME": "Your Name (Claude Code)",
"GIT_COMMITTER_EMAIL": "you+claude@gmail.com",
"GIT_CONFIG_COUNT": "3",
"GIT_CONFIG_KEY_0": "user.signingkey",
"GIT_CONFIG_VALUE_0": "ABCD1234EF567890",
"GIT_CONFIG_KEY_1": "commit.gpgsign",
"GIT_CONFIG_VALUE_1": "true",
"GIT_CONFIG_KEY_2": "gpg.format",
"GIT_CONFIG_VALUE_2": "openpgp"
}
}
Use an absolute path for GNUPGHOME, because JSON does not expand ~. If your file already has other settings, just add the env block next to them.
Step 4: Do the same for Codex
Create a second key in a second keyring. It is the same command as before, with a different folder, name and email:
mkdir -p ~/.gnupg-codex && chmod 700 ~/.gnupg-codex
GNUPGHOME=~/.gnupg-codex gpg --batch --pinentry-mode loopback --passphrase '' \
--quick-gen-key "Your Name (Codex) <you+codex@gmail.com>" ed25519 sign 1y
GNUPGHOME=~/.gnupg-codex gpg --list-secret-keys --keyid-format=long
Then repeat Step 2 with the Codex values: add you+codex@gmail.com as a verified email on GitHub, add the exported public key as a new GPG key (title it Codex (laptop)), and import the public key into your local keyring.
Codex reads its config from ~/.codex/config.toml. The [shell_environment_policy] section controls the environment of every command Codex runs, and its set key adds variables to it. Add this, using your Codex key ID and your real home path (TOML does not expand ~ either):
[shell_environment_policy.set]
GNUPGHOME = "/home/YOU/.gnupg-codex"
GIT_AUTHOR_NAME = "Your Name (Codex)"
GIT_AUTHOR_EMAIL = "you+codex@gmail.com"
GIT_COMMITTER_NAME = "Your Name (Codex)"
GIT_COMMITTER_EMAIL = "you+codex@gmail.com"
GIT_CONFIG_COUNT = "3"
GIT_CONFIG_KEY_0 = "user.signingkey"
GIT_CONFIG_VALUE_0 = "<CODEX_KEYID>"
GIT_CONFIG_KEY_1 = "commit.gpgsign"
GIT_CONFIG_VALUE_1 = "true"
GIT_CONFIG_KEY_2 = "gpg.format"
GIT_CONFIG_VALUE_2 = "openpgp"
GIT_CONFIG_COUNT is 3 because there are three config entries (numbered 0, 1 and 2). The third one, gpg.format = openpgp, is already git's default. I keep it anyway: if your global git config signs with SSH (gpg.format ssh), this makes sure the agent still uses its GPG key. If you change the number of entries, change GIT_CONFIG_COUNT too, or git ignores the extra ones.
If your file already has set = { ... } written inline under [shell_environment_policy], remove it. TOML does not allow the same table to be defined twice.
Why not a wrapper script? You could export these variables in a small script that starts Codex. But Codex has a built-in filter that removes inherited variables with names like *KEY* or *TOKEN*, to avoid leaking secrets. GIT_CONFIG_KEY_0 matches that pattern. The variable can disappear silently, and your commits go out unsigned. Values defined with set are added after that filter, so they are safe. One warning: if you also use include_only in this section, these variables must be on that list too.
Step 5: Test it
Start a new session of the agent (settings are read at startup), ask it to make a small commit, and then run this in your own terminal:
git log --show-signature -1
You should see something like:
gpg: Good signature from "Your Name (Claude Code) <you+claude@gmail.com>"
After you push, GitHub should show the commit as Verified, with the agent's name as the author.
Troubleshooting
-
Can't check signature: No public key: the commit is signed, but your personal keyring doesn't know the agent's public key. Do the import part of Step 2. -
gpg failed to sign the datainside Codex: the Codex sandbox is probably blocking gpg from writing to~/.gnupg-codex, where gpg-agent keeps its socket and lock files. Add the folder to the sandbox's writable folders (in my config this iswritable_rootsunder[sandbox_workspace_write]; check the Codex docs for your version). - The commit is signed with your personal key: the agent session started before you changed the settings. Restart it.
- GitHub shows Unverified: the commit email is not a verified email on your account, or the public key was not added. Check both parts of Step 2.
One honest limitation
This setup makes the agent use its own key by default. It does not stop an agent that can run any shell command from reading ~/.gnupg directly. If you want a real guarantee, also block access to that folder with Claude Code's sandbox or permission rules, or run the agent in a container.
Wrapping up
It takes about ten minutes per agent, and you get three things in return: your personal key never leaves your control, you can see in git log which agent wrote what, and your own signature goes back to meaning "I reviewed this".
If GPG feels too heavy, git can also sign commits with SSH keys (gpg.format ssh). The same idea works there: one key per agent, never your own.
Top comments (0)