Setup & configAlways fixable

Fix 'gpg failed to sign the data'

git commit fails with 'error: gpg failed to sign the data' — how do I fix commit signing?

If you’re seeing this error

error: gpg failed to sign the data
fatal: failed to write commit object
gpg: signing failed: Inappropriate ioctl for device
gpg: signing failed: No secret key

You’re in the right place — the fix is below.

Short answer

Test the signing outside Git first: echo test | gpg --clearsign. 'Inappropriate ioctl for device' means the passphrase prompt has nowhere to draw — add export GPG_TTY=$(tty) to your shell profile. 'No secret key' means user.signingkey does not match a key you hold. If you just want to commit right now, git commit --no-gpg-sign gets you through.

quick fix
export GPG_TTY=$(tty)
echo "test" | gpg --clearsign      # does signing work at all?
git config --global user.signingkey <KEYID>

The three checks that resolve nearly every signing failure

Does this match your situation?

  • 'error: gpg failed to sign the data' followed by 'fatal: failed to write commit object'.
  • 'gpg: signing failed: Inappropriate ioctl for device'
  • 'gpg: signing failed: No secret key'
  • Signing works in the terminal but fails from VS Code, an IDE or a cron job.
  • It worked for months and stopped — often exactly a year after you made the key.

Step-by-step fix

  1. Test GPG on its own

    Git adds nothing to the signing process — it shells out to gpg and reports whatever comes back. Testing directly tells you in one line whether this is a Git problem or a GPG problem, and it almost never is a Git problem.

    step 1
    echo "test" | gpg --clearsign
    gpg --list-secret-keys --keyid-format=long
    

    No output from --list-secret-keys means you have no private key on this machine — that is the whole bug.

  2. 'Inappropriate ioctl for device': give the prompt a terminal

    GPG needs to ask for your passphrase, and pinentry has to know which terminal to draw on. Without GPG_TTY it has nowhere to write the prompt and fails immediately. This is the single most common cause on Linux and macOS.

    step 2
    export GPG_TTY=$(tty)
    echo 'export GPG_TTY=$(tty)' >> ~/.bashrc     # or ~/.zshrc
    gpgconf --kill gpg-agent                      # restart the agent
    
  3. 'No secret key': point Git at a key you actually hold

    user.signingkey must name a key in your own keyring. Copying a key ID from an old dotfile, or setting it globally and then generating a different key, produces this exactly. Take the long ID from the sec line.

    step 3
    gpg --list-secret-keys --keyid-format=long
    # sec   ed25519/3AA5C34371567BD2  ->  the ID is 3AA5C34371567BD2
    git config --global user.signingkey 3AA5C34371567BD2
    git config --global commit.gpgsign true
    

    The email on the key must match user.email, or hosts will show the commit as unverified even once signing succeeds.

  4. Check whether the key simply expired

    GPG keys carry an expiry date and one or two years is a common default, which is why signing stops working out of nowhere long after you set it up. Extending the expiry keeps the same key and the same fingerprint.

    step 4
    gpg --list-keys --keyid-format=long        # look for [expired]
    gpg --edit-key <KEYID>
    # then: expire  ->  choose a period  ->  save
    

    After extending it, export the public key again and re-upload it to GitHub or GitLab so the new expiry is recorded there too.

  5. Tell Git which gpg binary to use

    Several systems ship both gpg and gpg2, and package managers move them around. If the wrong one is on Git's path it may have no access to your keyring at all.

    step 5
    which gpg gpg2
    git config --global gpg.program "$(which gpg)"
    
    Show the Windows / PowerShell version
    Windows / PowerShell
    (Get-Command gpg).Source
    git config --global gpg.program "C:/Program Files (x86)/GnuPG/bin/gpg.exe"
    
  6. Or switch to SSH signing and skip GPG entirely

    Git 2.34 can sign with the SSH key you already use to push. There is no keyring, no agent and no expiry, and GitHub and GitLab both verify it. For most people this is the better answer than debugging GPG.

    step 6
    git config --global gpg.format ssh
    git config --global user.signingkey ~/.ssh/id_ed25519.pub
    git config --global commit.gpgsign true
    

    Upload the same public key to your host a second time, as a signing key rather than an authentication key.

  7. Commit now, fix it afterwards

    Signing is a policy, not a requirement. If you are mid-task, turn it off for one commit or for this repository and come back to it.

    step 7
    git commit --no-gpg-sign -m "Fix login redirect"
    git config commit.gpgsign false        # this repository only
    

Why this works

Git does not implement signing. commit.gpgsign tells it to hand the commit object to an external program — gpg, or an SSH signer since 2.34 — and store the returned signature in the commit header. Every part of this failure therefore happens outside Git: the agent that holds your key, the pinentry program that asks for the passphrase, the terminal it needs to draw that prompt on, and the expiry date recorded in the key itself. Git only sees a non-zero exit code and refuses to write a commit it cannot sign, which is why 'gpg failed to sign the data' is followed by 'failed to write commit object'. Reproducing it with echo test | gpg --clearsign takes Git out of the picture in one line, and that is nearly always where the answer is.

If that didn’t work

  • Run GIT_TRACE=1 git commit -m test to see the exact gpg invocation Git is making.
  • In an IDE or a headless environment, set pinentry-mode loopback in ~/.gnupg/gpg.conf so no GUI prompt is needed.
  • On macOS, brew reinstall --cask gpg-suite repairs a pinentry that stopped working after an OS update.
  • In CI, import the key with --batch and use a passphrase-free subkey rather than an interactive agent.

How to stop it happening again

  • Put export GPG_TTY=$(tty) in your shell profile the day you set signing up.
  • Set a calendar reminder before your key expires, or create it with no expiry and rely on revocation.
  • Keep user.email and the key's UID identical, or hosts will not mark commits verified.
  • Consider SSH signing for new setups — one key, no agent, nothing to expire.

Commands used in this guide

git config

Read and write Git settings for one repo, your user, or the whole machine.

git commit

Record everything currently staged as a permanent snapshot.

git tag

Mark a specific commit as a release.

git log

Browse commit history with as much or as little detail as you want.

git commit --amend

Replace the most recent commit instead of stacking another one on top.

Still stuck?

Search the full command reference and every other rescue guide — there are 76 of them, covering everything from detached HEAD to force-push disasters.

Browse all guides →

Frequently asked questions

What causes 'error: gpg failed to sign the data'?

GPG returned a non-zero exit code, so Git refused to write the commit. The usual causes are a missing GPG_TTY so the passphrase prompt has no terminal, a user.signingkey that names a key you do not hold, an expired key, or Git calling the wrong gpg binary.

What does 'Inappropriate ioctl for device' mean when signing?

Pinentry could not find a terminal to display the passphrase prompt on. Add export GPG_TTY=$(tty) to your shell profile and restart the agent with gpgconf --kill gpg-agent. It is the most common cause of this error on Linux and macOS.

How do I commit without signing, just this once?

git commit --no-gpg-sign -m "message". To disable it for one repository, run git config commit.gpgsign false — your global setting is untouched, so other projects keep signing.

Why did commit signing stop working after a year?

Your GPG key almost certainly expired — one or two years is a common default. Run gpg --list-keys and look for [expired], then extend it with gpg --edit-key <id> and the expire command. Re-upload the public key afterwards so your host records the new date.

Can I sign commits without GPG?

Yes. Git 2.34 and later can sign with an SSH key: git config --global gpg.format ssh and point user.signingkey at your public key file. There is no keyring, no agent and no expiry, and both GitHub and GitLab verify SSH-signed commits.

Related rescue guides

Always fixable

git commit opened a text editor I can't get out of — how do I save and exit?

You are in Vim. Press Esc, type :wq and press Enter to save and finish the commit, or :q! to abandon it. For a log or diff that has taken over the terminal you are in the pager instead — press q. Set git config --global core.editor "code --wait" (or nano) so it never happens again.

Read the fix →
Always fixable

My commits show the wrong name or email — how do I fix them?

Set the correct identity with git config, then fix the last commit with git commit --amend --reset-author. For a range of commits use git rebase with an exec step, or git filter-repo --mailmap to rewrite the whole history at once.

Read the fix →
Always fixable

git clone or push fails with Permission denied (publickey) — how do I fix my SSH key?

The server didn't accept any key your SSH client offered. Either you have no key, the key isn't loaded into the agent, or its public half was never added to your account. Run ssh -T git@github.com to see exactly which keys are being tried.

Read the fix →
Always fixable

My pre-commit hook (or Husky) isn't running — why does Git ignore it?

Nine times out of ten the hook file is not executable, is misnamed, or sits in a folder Git isn't reading. Run git config core.hooksPath to see where Git actually looks, check the file is named exactly pre-commit with no extension, then chmod +x it. A stray global core.hooksPath silently overrides every repository on the machine — including Husky's.

Read the fix →
← All Git Rescue guides