git clone / SSH to GitHub hangs on Fedora + GNOME (gcr-ssh-agent)

Published 16 June 2026

date
env
linux Fedora 44 (GNOME) — gnome-keyring 1:46+, gcr-4 macos N/A

Symptom

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: ssh_get_authentication_socket_path: path '/run/user/1000/gcr/ssh' <- agent = GNOME gcr
debug1: Server accepts key: ... ED25519 ...
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:

ExecStartPost=-/usr/bin/systemctl --user set-environment SSH_AUTH_SOCK=%t/gcr/ssh

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
IdentitiesOnly yes
AddKeysToAgent yes

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 --user enable --now ssh-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

Terminal window
systemctl --user mask gcr-ssh-agent.socket gcr-ssh-agent.service
systemctl --user stop gcr-ssh-agent.socket gcr-ssh-agent.service
# Fix the *current* session without logging out:
systemctl --user set-environment SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/ssh-agent.socket

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.
secret-tool store --label="ssh: id_github" unique "ssh-store:$HOME/.ssh/id_github"

b. Askpass helper — ~/.local/bin/ssh-askpass-secret.sh

#!/usr/bin/env bash
exec secret-tool lookup unique "$HOME/.ssh/id_github" 2>/dev/null \
|| secret-tool lookup unique "ssh-store:$HOME/.ssh/id_github"

c. Loader — ~/.local/bin/load-ssh-key.sh

#!/usr/bin/env bash
export SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/ssh-agent.socket"
export SSH_ASKPASS="$HOME/.local/bin/ssh-askpass-secret.sh"
export SSH_ASKPASS_REQUIRE=force # force ssh-add to use the askpass helper
ssh-add "$HOME/.ssh/id_github" </dev/null # no controlling TTY -> askpass is used
Terminal window
chmod +x ~/.local/bin/ssh-askpass-secret.sh ~/.local/bin/load-ssh-key.sh

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:

Terminal window
secret-tool lookup unique "ssh-store:$HOME/.ssh/id_github" # what's stored
ssh-keygen -y -P "<that value>" -f ~/.ssh/id_github # works => value is correct
# If wrong: clear and re-store the REAL passphrase
secret-tool clear unique "ssh-store:$HOME/.ssh/id_github"
secret-tool store --label="ssh: id_github" unique "ssh-store:$HOME/.ssh/id_github"

Never write the passphrase value into this doc.


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 no pam_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:
echo "$SSH_AUTH_SOCK" # -> /run/user/1000/ssh-agent.socket
systemctl --user show-environment | grep SSH_AUTH_SOCK # same value at session level
systemctl --user list-unit-files 'gcr-ssh-agent.*' # both should read: masked
ssh-add -l # lists id_github once loaded
# The real test — should answer instantly, never hang:
ssh -o BatchMode=yes -T git@github.com # "Hi <user>! You've successfully authenticated"
# Pinpoint a future hang:
ssh -vvv -T git@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).


Sources