getsops/sops · error

failed to execute command %s: %w

Error message

failed to execute command %s: %w

What it means

The command configured in SOPS_AGE_SSH_PRIVATE_KEY_CMD was parsed successfully and executed via exec.Command(...).Output(), but it returned a non-zero exit status or failed to start. SOPS wraps that error, so the underlying message (e.g. 'exit status 1' or 'executable file not found') is chained in %w. This means SOPS could not obtain age identities from that command.

Source

Thrown at age/keysource.go:313

}

// getOutputFromCmd executes a shell command provided in param 'cmdString',
// optionally adding env vars provided in param 'envVars',
// and returns the command's output and error
func getOutputFromCmd(cmdString string, envVars []string) ([]byte, error) {
	var out []byte

	args, err := shlex.Split(cmdString)
	if err != nil {
		return nil, fmt.Errorf("failed to parse command %s: %w", cmdString, err)
	}
	cmd := exec.Command(args[0], args[1:]...)
	if envVars != nil {
		cmd.Env = append(os.Environ(), envVars[0:]...)
	}
	out, err = cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to execute command %s: %w", cmdString, err)
	}

	return out, nil
}

// loadAgeSSHIdentity attempts to load age SSH identities in this order:
// 1. An SSH private key from the SopsAgeSshPrivateKeyFileEnv environment variable.
// 2. An SSH private key returned by executing the command from the
// SopsAgeSshPrivateKeyCmdEnv environment variable
// 3. `~/.ssh/id_ed25519` or `~/.ssh/id_rsa`.
// If no age SSH identity is found, it will return nil.
func (key *MasterKey) loadAgeSSHIdentities() ([]age.Identity, []string, errSet) {
	var identities []age.Identity
	var unusedLocations []string
	var errs errSet

	sshKeyFilePath, ok := os.LookupEnv(SopsAgeSshPrivateKeyFileEnv)
	if ok {

View on GitHub (pinned to 13442bb981)

Solutions

  1. Run the command from SOPS_AGE_SSH_PRIVATE_KEY_CMD directly in a shell and check its exit status/output; fix whatever it reports (missing key, locked store, etc.).
  2. Verify the binary exists and is executable: command -v <binary> and check the PATH in the environment running sops.
  3. If it uses ssh-agent, ensure the agent is running and the identity is added (ssh-add -l).
  4. For pass/gopass-backed commands, initialize/unlock the store and confirm the exact entry name exists.
  5. Wrap the command in a script that logs stderr so the real failure is visible, then point the env var at it.

Example fix

// before
export SOPS_AGE_SSH_PRIVATE_KEY_CMD="pass show age-key"  # entry doesn't exist
// failed to execute command pass show age-key: exit status 1

// after
pass ls | grep age-key            # confirm the entry name
export SOPS_AGE_SSH_PRIVATE_KEY_CMD="pass show age/identity"
sops -d secrets.enc.yaml
Defensive patterns

Strategy: try-catch

Validate before calling

// shell: pre-flight the command exactly as sops would run it
CMD="$SOPS_AGE_SSH_PRIVATE_KEY_CMD"
$CMD > /dev/null || { echo "key command failed: $?"; exit 1; }
command -v "${CMD%% *}" >/dev/null || { echo "binary not on PATH"; exit 1; }

Try / catch

out, err := exec.Command("sops", "-d", file).Output()
var ee *exec.ExitError
if errors.As(err, &ee) && strings.Contains(string(ee.Stderr), "failed to execute command") {
    return fmt.Errorf("SOPS_AGE_SSH_PRIVATE_KEY_CMD binary failed; check agent/store: %w", err)
}

Prevention

When it happens

Trigger: getOutputFromCmd runs the SOPS_AGE_SSH_PRIVATE_KEY_CMD binary; error occurs if the binary is missing, not executable, or exits non-zero (no matching key in ssh-agent, pass/gopass entry missing, wrong passphrase).

Common situations: ssh-agent not running or key not loaded; pass store entry absent on a new machine; typo in binary name; CI environment lacking the secret store; script failing due to missing HOME or TTY.

Related errors


AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01). Data as JSON: /api/errors/8cd3f5f54463032a. Report an issue: GitHub.