git clone git@github.com:... (or any ssh git@github.com) hangs forever:
Cloning into 'moodaura'...
<- stuck here, no error, no password prompt, never returns
ssh-add -l shows the key is loaded, the SSH config looks correct, and the
network is fine — yet nothing happens.
Root cause (confirmed)
The hang is not a network/firewall problem and not a key/config problem.
It is GNOME’s gcr-ssh-agent (GNOME Keyring’s SSH agent, socket
/run/user/1000/gcr/ssh) that freezes when asked to sign the
authentication challenge with a passphrase-protected key.
Verbose SSH (ssh -vvv -T git@github.com) shows exactly where it stalls — the
server accepts the key, then the agent never returns the signature:
debug3: sign_and_send_pubkey: signing using ssh-ed25519 ... <- asks agent to SIGN
<- HANGS HERE until timeout
Background:
Since gnome-keyring 1:46, SSH support was disabled in the daemon and moved
into gcr-ssh-agent (package gcr-4). This new agent is the one that hangs.
Network checks confirmed port 22 reaches GitHub instantly
(Permission denied (publickey) when the agent is bypassed) — so no need
for the port-443 workaround.
The detail that bit twice: gcr re-hijacks SSH_AUTH_SOCK
Even with a healthy OpenSSH agent running, the hang came back because the
gcr-ssh-agent.socket user unit contains:
Every GNOME login, gcr starts and overwrites SSH_AUTH_SOCK in the systemd
user environment back to the gcr socket — which every new terminal/IDE then
inherits. So your key gets loaded into the freezing agent again and git hangs.
The gcr units show disabled but GNOME still pulls the socket in (it ends up
active (running)). Bypassing gcr is not enough — you must stop it from
setting the variable.
Solution implemented (current — 2026-06-16)
Run one OpenSSH ssh-agent as a systemd user service, export its socket to
the whole login session via environment.d, and mask gcr-ssh-agent so it can
never reclaim SSH_AUTH_SOCK.
1. ~/.ssh/config
Host github.com
User git
Hostname github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_github
IdentitiesOnlyyes
AddKeysToAgentyes
2. ~/.config/systemd/user/ssh-agent.service — one agent per session
[Unit]
Description=SSH key agent (one per login session)
[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
[Install]
WantedBy=default.target
Terminal window
systemctl--userenable--nowssh-agent.service
3. ~/.config/environment.d/ssh-agent.conf — point the whole session at it
# Every process in the login session (terminals, IDE, Claude Code) uses the
# single ssh-agent run by ssh-agent.service. Load the key once per boot:
# ssh-add ~/.ssh/id_github
SSH_AUTH_SOCK=${XDG_RUNTIME_DIR}/ssh-agent.socket
4. Mask gcr-ssh-agent — the step that actually stops the hang
Masking writes symlinks to /dev/null under ~/.config/systemd/user/, so it
persists across reboots. gnome-keyring’s other functions (Wi-Fi, stored
passwords) are a different daemon and are unaffected.
To undo:systemctl --user unmask gcr-ssh-agent.socket gcr-ssh-agent.service
Old approach (superseded): a ~/.zshrc snippet that started its own
ssh-agent (in ~/.ssh/agent.env) and overrode SSH_AUTH_SOCK, tracking the
agent via kill -0 $SSH_AGENT_PID. It only covered terminals (not the IDE) and
didn’t stop gcr from clobbering the variable — replaced by the systemd service
gcr masking above.
5. Type the passphrase once ever — silent auto-load from the keyring
The systemd agent above is persistent (one passphrase per boot, not per
terminal). To go one step further and never be prompted again, store the
passphrase in the GNOME login keyring once and have login load the key silently
into the OpenSSH agent via a custom SSH_ASKPASS helper.
a. Store the passphrase in the keyring (once, by hand)
Terminal window
# Type the REAL passphrase when prompted; it is saved encrypted in the login keyring.
d. Run it at login — ~/.config/autostart/ssh-add.desktop
[Desktop Entry]
Type=Application
Name=Load SSH key into agent
Exec=/home/USER/.local/bin/load-ssh-key.sh
X-GNOME-Autostart-enabled=true
NoDisplay=true
Result: the passphrase is typed exactly once, ever. Every login, the loader
pulls it from the (already-unlocked) keyring and loads the key into the signing-
capable OpenSSH agent with no prompt and no hang.
⚠️ STALE-SECRET trap (cost a long debugging saga): if a wrong/old
passphrase is stored in the keyring, the load fails silently and the prompt
seems to “come back”. Diagnose, then fix:
6. The fingerprint caveat — keyring is NOT unlocked on biometric login
If you log in with your password, the keyring auto-unlocks (PAM
pam_gnome_keyring.so is in /etc/pam.d/gdm-password), so the loader above runs
silently → zero prompts. ✅
If you log in with the fingerprint, you will instead see an “Unlock Login
Keyring — Authentication required” dialog (sometimes two, from different
prompters racing). This is not the SSH passphrase and not a bug in this
setup:
/etc/pam.d/gdm-fingerprint has nopam_gnome_keyring.so, and fprintd
never sees your password.
Your password is the cryptographic key that decrypts the login keyring.
A fingerprint produces slightly different readings each scan, so no reproducible
key can be derived from it — there is no way to unlock the keyring from a
fingerprint. This is a fundamental limitation, not a config gap.
Recommended (standard, no security downgrade): accept it. Either log in with
your password (keyring unlocks, zero prompts), or, after a fingerprint login,
type your password once in the unlock dialog. The fingerprint is still great
for sudo and unlocking after sleep/screen-lock.
Do NOT “fix” this by setting an empty keyring password — that stores
all your secrets (Wi-Fi, browser, SSH passphrase) unencrypted at rest. A
TPM-backed auto-unlock exists in theory but is non-standard / poorly supported
on Fedora today.
Scope: terminals and GUI apps both use the single OpenSSH agent via
environment.d (§3) — gcr is masked, so VS Code’s git etc. use the same agent.
Quick verification
Terminal window
# Our agent (not gcr) is in use — must end in ssh-agent.socket, NOT gcr/ssh:
ssh-vvv-Tgit@github.com# watch for a stall right after "sign_and_send_pubkey"
Gotcha: a terminal opened before the fix keeps the old
SSH_AUTH_SOCK=/run/user/1000/gcr/ssh it inherited at startup. Open a fresh
terminal (or export SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/ssh-agent.socket).