# Git - Commit Signing with SSH Agents and gitsign
SSH commit and tag signing fails in specific, diagnosable ways. The wrong `SSH_AUTH_SOCK` hides the signing key from non-interactive shells. A signing wrapper like gitsign needs its `gpg.ssh.program` entry, and a cryptographic signature does not replace a `Signed-off-by:` trailer.
## Key points
- When `SSH_AUTH_SOCK` points at the password-manager agent instead of the mux socket, the gitsign signing key is invisible and commits fail with `failed to write commit object`. Prefix git with the mux socket path.
- The socket pin belongs in `.zshenv`, the only file every zsh reads. A `.zshrc` pin reaches only interactive shells, so tool-spawned shells inherit the wrong socket. Signing then fails as socket inheritance, not a key problem.
- `SSH_AUTH_SOCK` alone does not enable signing when `gpg.ssh.program` is unset. The fallback `ssh-keygen` refuses the forced socket. Route signing through the configured program.
- Signed release tags must be annotated: `git tag -a <tag> <sha> -m "<msg>"`. A signing configuration that requires a tag message cannot sign a lightweight tag.
- Allowed-signers entries are `<email> <key-type> <public-key>`, one per line, with multiple emails allowed per key. Verify the file parses with `ssh-keygen -l`.
- An SSH key can authenticate yet lack organization SSO authorization for signing. A managed account may need a separate registered signing key and a refreshed `admin:ssh_signing_key` scope.
- Export `SSH_AUTH_SOCK="$HOME/.ssh/ssh-agent-mux.sock"` so one agent session serves both the password-manager auth keys and the gitsign signing key. The inline export fixes running shells, and the `.zshenv` pin heals new ones.
- Verify mux routing in isolation: `ssh-add -l` for the union key listing, then a signed `git commit`, then an SSH auth test. Failures localize to a layer.
- On macOS, `launchctl getenv SSH_AUTH_SOCK` finds the launchd agent socket. It resolves to the mux socket when the interactive one points at the password manager.
- Password-manager agents fail intermittently from per-use approval expiry, not key absence. The key signs standalone while in-the-moment git calls fail. Retry after the approval lands.
- When the agent socket is broken but GitHub CLI auth is valid, push via HTTPS. The CLI holds a token in the system keyring and pushes without touching SSH config.
- `git commit -s` adds a `Signed-off-by:` trailer, a legal attestation distinct from cryptographic signing. A commit can be SSH-signed and still fail DCO. Neither implies the other.
- A commit pushed without the trailer has no non-force remedy. `git commit --amend -s && git push --force-with-lease` with owner approval is the only recovery path, safe against races.
- GitHub squash merges authored under a private-email noreply address fail DCO when the sign-off uses the real address. The sign-off matches neither author nor committer, because GitHub sets the author itself. Fix it account-level: uncheck "Keep my email addresses private", or use the noreply address as the git identity.
## How to apply
1. Pin `export SSH_AUTH_SOCK="$HOME/.ssh/ssh-agent-mux.sock"` in `.zshenv`. Inline `SSH_AUTH_SOCK=~/.ssh/ssh-agent-mux.sock git commit -S` in a running shell.
2. Set `gpg.ssh.program` to gitsign, not the `ssh-keygen` fallback.
3. Verify layers alone: `ssh-add -l`, signed commit, SSH auth test. On macOS, `launchctl getenv SSH_AUTH_SOCK` shows the launchd socket.
4. Allowed-signers lines: `<email> <key-type> <public-key>`, checked with `ssh-keygen -l`.
5. Release tags: `git tag -a <tag> <sha> -m "<tag>"`.
6. DCO: `git commit -s` on every commit. Recover with `git commit --amend -s && git push --force-with-lease` (owner approval).
7. Squash merges: uncheck "Keep my email addresses private", or use the noreply address as the git identity.
8. Broken socket, valid GitHub CLI auth: push over HTTPS.
9. Managed accounts: register a separate signing key and refresh the `admin:ssh_signing_key` scope.
10. Retry a password-agent signature after the approval lands.
## Observations
- [ssh] An `SSH_AUTH_SOCK` at the password manager hides the gitsign signing key. Prefix git with the mux socket. #git (source: agent sessions, 2026-06)
- [shell] The mux pin belongs in `.zshenv` so non-interactive shells inherit the right socket. #zsh (source: agent sessions, 2026-06)
- [git] Signing needs `gpg.ssh.program` set. The `ssh-keygen` fallback refuses the forced socket. #git (source: agent sessions, 2026-09)
- [git] Signed release tags must be annotated. The signing config requires a tag message. #git (source: agent sessions, 2026-07)
- [ssh] Allowed-signers lines are `<email> <key-type> <public-key>`. Verify with `ssh-keygen -l`. #ssh (source: agent sessions, 2026-06)
- [security] An SSH key can authenticate yet lack SSO authorization for signing. Managed accounts need a registered signing key and the `admin:ssh_signing_key` scope. #github (source: agent sessions, 2026-09)
- [workflow] Verify mux routing layer by layer: `ssh-add -l`, signed commit, SSH auth test. #git (source: agent sessions, 2026-06)
- [ops] Password-manager signatures fail intermittently from approval expiry. Retry after the approval lands. #ssh (source: agent sessions, 2026-10)
- [git] Push via HTTPS when the socket is broken but GitHub CLI auth is valid. #github (source: agent sessions, 2026-07)
- [dco] A cryptographic signature and a `Signed-off-by:` trailer are independent attestations. #dco (source: agent sessions, 2026-07)
- [dco] Squash merges under a noreply address fail DCO when the sign-off uses the real address. Fix at the account level. #dco (source: agent sessions, 2026-10)
## Relations
- relates_to [[resources/documentation/Git Configuration]] (git config keys like `gpg.ssh.program`)
- relates_to [[resources/documentation/Zsh Configuration]] (socket pin lives in `.zshenv`)
- relates_to [[resources/documentation/SSH - Debugging Config, Agents, and Forwards]] (agent socket mechanics)
- relates_to [[resources/documentation/Git - Rebase, Stash, and Recovery]] (`--force-with-lease`)
## Sources
Personal agent-session notes, April 2026 to October 2026