From 01b1aeea3b297d13b301631b736dfc09c6318716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 23 Jun 2026 00:21:29 +0000 Subject: [PATCH] Improved ssh-add handling for macos --- CHANGELOG.md | 5 +++++ internal/session/session.go | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81a0b2e..3e2ff64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/). +## [0.10.0] + +### Fixed +- Auto-`ssh-add` no longer misfires in non-interactive contexts. It now gates on **stdin** being a terminal (how `ssh-add` itself decides whether to prompt) and forces ssh-add to use the terminal via `SSH_ASKPASS_REQUIRE=never`, never a GUI askpass. Fixes a noisy `ssh_askpass: exec(/usr/X11R6/bin/ssh-askpass): No such file or directory` error on macOS when running `vars resolve` from a shell hook / `direnv` / piped stdin (stderr was a TTY but stdin wasn't); those contexts now get just the clean "load it with `ssh-add `" message. + ## [0.9.0] ### Added diff --git a/internal/session/session.go b/internal/session/session.go index 4a1ae34..d55fbeb 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -245,21 +245,27 @@ func ensureSigner(fingerprint string) (*sshderive.Signer, error) { } // canPromptForKey reports whether ssh-add could succeed: an agent to add the key -// to, and a terminal to prompt the passphrase on. A real TTY is required (not -// SSH_ASKPASS) so non-interactive contexts get a clean error instead of hanging -// on a passphrase that can never arrive. Use `ssh -t host vars …` to get a TTY. +// to, and an interactive terminal on stdin. It checks stdin specifically because +// ssh-add decides to prompt on the terminal vs. fall back to a GUI askpass based +// on whether stdin is a tty; when it isn't (a shell hook, direnv, piped stdin) it +// tries SSH_ASKPASS, whose macOS default (/usr/X11R6/bin/ssh-askpass) isn't +// installed and errors. Gating on stdin keeps those contexts on the clean +// "load it with ssh-add" message. Use `ssh -t host vars …` to get a TTY over SSH. func canPromptForKey() bool { if os.Getenv("SSH_AUTH_SOCK") == "" { return false } - return term.IsTerminal(int(os.Stdin.Fd())) || term.IsTerminal(int(os.Stderr.Fd())) + return term.IsTerminal(int(os.Stdin.Fd())) } -// runSSHAdd loads a specific key file into the agent. ssh-add prompts on the -// terminal; its stdout goes to stderr so it never pollutes command output. +// runSSHAdd loads a specific key file into the agent. SSH_ASKPASS_REQUIRE=never +// forces ssh-add to prompt on the terminal rather than exec a GUI askpass (which +// is usually absent), so it can't fail with a missing-askpass error. Its stdout +// goes to stderr so it never pollutes command output. func runSSHAdd(path string) error { cmd := exec.Command("ssh-add", path) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stderr, os.Stderr + cmd.Env = append(os.Environ(), "SSH_ASKPASS_REQUIRE=never") return cmd.Run() }