diff --git a/crates/askpass/src/askpass.rs b/crates/askpass/src/askpass.rs index af5d0d31ad..8092e36f3f 100644 --- a/crates/askpass/src/askpass.rs +++ b/crates/askpass/src/askpass.rs @@ -504,11 +504,17 @@ fn generate_gpg_wrapper_script( .try_quote_prefix_aware("Enter passphrase for your Git signing key:") .context("Failed to shell-escape gpg passphrase prompt")?; - // The wrapper inspects gpg's arguments and only prompts for a passphrase when - // git asks it to *sign* (e.g. `gpg -bsau `). Other invocations such as - // signature verification (`--verify`) run gpg unchanged so we never pop a - // spurious modal. When signing, we feed the passphrase to gpg on fd 3 via - // `--passphrase-fd 3` in loopback mode, so no pinentry/terminal is required. + // The wrapper only intervenes when git asks gpg to *sign* (e.g. `gpg -bsau + // `); other invocations like `--verify` run unchanged. For signing we + // first try plain gpg so gpg-agent/keychain can supply a cached or empty + // passphrase silently, and only fall back to asking Zed (loopback mode, fd + // 3) when that fails, e.g. the "Inappropriate ioctl for device" case with no + // TTY for pinentry. + // + // git streams the payload on stdin (readable once) and reads the signature + // from stdout, so we buffer stdin to replay it into both attempts and buffer + // the first attempt's output, forwarding it only if it succeeds. The + // passphrase goes to fd 3 via a pipe. Ok(format!( r#"#!/bin/sh for arg in "$@"; do @@ -527,11 +533,29 @@ if [ -z "${{is_signing}}" ]; then exec {gpg_program} "$@" fi -# Signing: ask Zed for the passphrase, then hand it to gpg on fd 3. +# Signing. Buffer stdin (the payload) and the first attempt's output +# so we can retry cleanly on failure without git seeing partial output. +tmpdir=$(mktemp -d) || exit 1 +trap 'rm -rf "$tmpdir"' EXIT +payload="$tmpdir/payload" +signature="$tmpdir/signature" +status="$tmpdir/status" +cat > "$payload" || exit 1 + +# First try letting gpg-agent/keychain supply the passphrase without any +# interactive pinentry. If that succeeds (cached passphrase) +# forward its output and we're done, so Zed never shows a modal. +if {gpg_program} --pinentry-mode error "$@" < "$payload" > "$signature" 2> "$status"; then + cat "$status" >&2 + cat "$signature" + exit 0 +fi + +# The silent attempt failed: ask Zed for the passphrase, then hand it to gpg on +# fd 3 using loopback mode so no pinentry/terminal is required. passphrase=$(printf '%s\0' {prompt} | {askpass_program} --askpass={askpass_socket} 2>/dev/null) -exec {gpg_program} --pinentry-mode loopback --passphrase-fd 3 "$@" 3<