GPG_TTY and the Inappropriate ioctl Error
GPG_TTY points gpg-agent at the current terminal so pinentry can prompt; in CI there is no terminal, so you bypass it.
The classic "Inappropriate ioctl for device" failure comes from gpg trying to read a passphrase with no terminal. Interactively you fix it with GPG_TTY; in CI you skip the prompt entirely.
What it does
GnuPG 2.x delegates passphrase entry to gpg-agent, which launches a pinentry program on a terminal. GPG_TTY tells the agent which terminal that is. When unset (or wrong), pinentry cannot attach and signing fails with an ioctl error.
Common usage
# interactive shells: export the current tty
export GPG_TTY=$(tty)
# CI: there is no tty, so do not rely on GPG_TTY at all
gpg --batch --pinentry-mode loopback \
--passphrase-fd 0 --detach-sign file.tar.gzOptions
| Setting | What it does |
|---|---|
| GPG_TTY=$(tty) | Tell gpg-agent the controlling terminal (interactive) |
| --pinentry-mode loopback | Bypass pinentry; gpg handles the passphrase (CI) |
| --passphrase-fd <n> | Feed the passphrase without a terminal |
| pinentry-mode loopback (gpg.conf) | Make loopback the default in config |
In CI
GPG_TTY only helps when a real terminal exists. On a runner there is none, so do not chase GPG_TTY; instead disable the prompt with --pinentry-mode loopback and supply the passphrase via --passphrase-fd. The ioctl error is a symptom of an attempted prompt, not of a missing GPG_TTY per se.
Common errors in CI
"gpg: signing failed: Inappropriate ioctl for device" is the headline symptom: gpg-agent tried to open pinentry on a terminal that does not exist. The CI fix is --pinentry-mode loopback plus a supplied passphrase, not setting GPG_TTY. "No pinentry" or "No such file or directory" means no pinentry program is installed at all.