Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path>`" message.

## [0.9.0]

### Added
Expand Down
18 changes: 12 additions & 6 deletions internal/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
Loading