GPG pinentry terminal conflict: signing fails with "No passphrase given" when using terminal-based pinentry
Summary
When commit.gpgsign=true is configured and the GPG passphrase is not cached, git commit via Claude Code's Bash tool triggers pinentry-curses which cannot read terminal input. The commit fails with gpg: signing failed: No passphrase given.
This affects all terminal-based pinentry variants (pinentry-curses, pinentry-tty) and all interactive TUI programs spawned by the Bash tool.
Steps to Reproduce
- Configure GPG commit signing (
git config --global commit.gpgsign true) - Ensure passphrase is not cached (
gpgconf --kill gpg-agent) - Start Claude Code
- Ask Claude to create a git commit
git committriggersgpg-agent→pinentry-curseslaunches
Observed Behavior
Pinentry launches but cannot receive keyboard input:
[GNUPG:] PINENTRY_LAUNCHED <PID> curses 1.2.1 /dev/pts/N xterm-256color :0 <UID>/<GID>/5 <GID>/<GID> -
gpg: signing failed: No passphrase given
fatal: failed to write commit object
Key diagnostic facts:
- Pinentry was launched and targeted the correct TTY
GPG_TTYwas set correctly (inherited from parent shell)- Error is
No passphrase given— pinentry could not read user input
Root Cause
Terminal input contention between Claude Code's Ink renderer and pinentry:
Claude Code (Ink renderer) ← reads from process.stdin / terminal
└── Bash Tool (stdio: 'pipe')
└── git commit → gpg-agent → pinentry-curses
└── opens /dev/tty ← ALSO reads from terminal
Both Claude Code's Ink renderer and pinentry-curses read from the same PTY simultaneously. Keystrokes are consumed by the Ink renderer instead of reaching pinentry.
Setting GPG_TTY does not help — the problem is not TTY resolution, it is exclusive terminal input ownership.
Scope
Affects all interactive TUI programs spawned by the Bash tool:
pinentry-curses/pinentry-ttyvim/nano(if git opens an editor)ssh/sudopassword prompts- Interactive build tools (turborepo, etc.)
Not affected: GUI pinentry (pinentry-gnome3, pinentry-qt, pinentry-mac) since they open their own window.
Workarounds
- Pre-cache passphrase (in a separate terminal):
echo "test" | gpg --clearsign > /dev/null - Use
--no-gpg-sign:git commit --no-gpg-sign -m "message" - Switch to GUI pinentry:
pinentry-program /usr/bin/pinentry-gnome3in~/.gnupg/gpg-agent.conf - Increase cache timeout:
default-cache-ttl 86400in~/.gnupg/gpg-agent.conf
Proposed Fix
Claude Code should temporarily pause the Ink renderer and release terminal control when a subprocess needs interactive input. For GPG pinentry specifically:
- Detect that a git command may trigger GPG signing
- Pause the Ink renderer (stop reading stdin, reset keyboard modes)
- Spawn the command with
stdio: ['inherit', 'pipe', 'pipe'] - Resume the Ink renderer when the command completes
A workaround plugin (gpg-pinentry-guard) is available in PR #30521.
Environment
- Claude Code 2.1.63
- WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2) / Windows Terminal
- GnuPG 2.4.4, pinentry-curses 1.2.1
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗