ewjoachim: Hi ! Thanks for your contribution !

Let me know if you would rather:
- Finish it (write missing tests)
- Have us finish it for you
- Finish it with us
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 988012c..e75aad1 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -7,6 +7,7 @@ Changelog
 - Verbosity can now be controlled through ``verbose:`` option in config file
 - New option ``umask: / --umask / VAULT_CLI_UMASK`` controls permissions of new files (#162)
 - Forced dependency pyyaml>=5.3.1 to avoid CVE (#156)
+- Silenced ``ssh-add`` in ``vault-cli ssh`` (#167)
 
 1.3.1 (2020-04-16)
 ------------------
diff --git a/vault_cli/cli.py b/vault_cli/cli.py
index 1e1cf03..2a00471 100644
--- a/vault_cli/cli.py
+++ b/vault_cli/cli.py
@@ -59,7 +59,14 @@ def handle_errors():
     try:
         yield
     except exceptions.VaultException as exc:
-        raise click.ClickException(str(exc))
+        messages = []
+        while True:
+            exc_str = str(exc).strip()
+            messages.append(f"{type(exc).__name__}: {exc_str}")
+            exc = exc.__cause__ or exc.__context__
+            if not exc:
+                break
+        raise click.ClickException("\n".join(messages))
 
 
 def print_version(ctx, __, value):
diff --git a/vault_cli/exceptions.py b/vault_cli/exceptions.py
index ccfce57..38e06a6 100644
--- a/vault_cli/exceptions.py
+++ b/vault_cli/exceptions.py
@@ -80,3 +80,7 @@ class VaultSealed(VaultAPIException):
 
 class VaultInvalidEnvironmentName(VaultException):
     pass
+
+
+class VaultSubprocessException(VaultException):
+    pass
diff --git a/vault_cli/ssh.py b/vault_cli/ssh.py
index 0d7a620..32a7264 100644
--- a/vault_cli/ssh.py
+++ b/vault_cli/ssh.py
@@ -4,6 +4,7 @@ import sys
 from typing import Mapping, Optional, Sequence
 
 from vault_cli import environment as env_module
+from vault_cli import exceptions
 
 SSH_PASSPHRASE_ENVVAR = "VAULT_CLI_SSH_PASSPHRASE"
 
@@ -18,11 +19,20 @@ def _launch_command(
     command: Sequence[str], stdin: str, environment: Mapping[str, str]
 ) -> str:
     environment = env_module.full_environment(environment)
-    process = subprocess.Popen(
-        command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=environment
+    process = subprocess.run(
+        command,
+        input=stdin,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        env=environment,
+        encoding="utf-8",
     )
-    stdout, _ = process.communicate(input=stdin.encode("utf-8"))
-    return stdout.decode("utf-8")
+    try:
+        process.check_returncode()
+    except subprocess.CalledProcessError as exc:
+        raise exceptions.VaultSubprocessException(process.stderr.strip()) from exc
+
+    return process.stdout.strip()
 
 
 def add_key(key: str, passphrase: Optional[str] = None) -> None:

[Vault_cli][SSH] ssh-add message
Bonjour à tous, 

lors de l'utilisation de vault_cli avec l'option ssh, le message de ssh-add s'affiche.

```
/usr/local/bin/vault --config-file /etc/{SERVICE}/vault_ro.yml ssh --key {PATH}:{VALUE}

Identity added: (stdin) ((stdin))
```

Est-il possible de rendre cette affichage optionnel afin de pouvoir le désactiver (notamment lors de l'utilisation via cron) ?
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
index b7fa4a8..741e03a 100644
--- a/tests/integration/test_integration.py
+++ b/tests/integration/test_integration.py
@@ -84,7 +84,10 @@ c:
     assert result.exit_code == 1
     assert (
         result.output
-        == "Error: Secret not found\nKey 'foo' not found in secret at path '/secretkvv1/c/d'\n"
+        == """Error: VaultSecretNotFound: Secret not found
+Key 'foo' not found in secret at path '/secretkvv1/c/d'
+KeyError: 'foo'
+"""
     )
 
     assert call(cli_runner, ["list"]).output == "c/\n"
@@ -190,11 +193,16 @@ Fh34DrZLZim42czNi6I+ww6+/y68rkmExwToM=
         cli_runner,
         ["set", "ssh_key", f"private={ssh_private}", f"passphrase={ssh_passphrase}"],
     )
-    identities = subprocess.check_output(
+    identities = subprocess.run(
         "vault-cli ssh --key ssh_key:private --passphrase ssh_key:passphrase "
-        "-- ssh-add -L".split()
+        "-- ssh-add -L".split(),
+        check=True,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
     )
-    assert ssh_public in identities.decode("utf-8")
+    assert ssh_public in identities.stdout.decode("utf-8")
+    assert "Identity added" not in identities.stdout.decode("utf-8")
+    assert identities.stderr.decode("utf-8") == ""
 
 
 @pytest.fixture
diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
index dccd9b5..3196c7a 100644
--- a/tests/unit/test_cli.py
+++ b/tests/unit/test_cli.py
@@ -619,11 +619,13 @@ def test_lookup_token(cli_runner, vault_with_token):
 def test_handle_errors(cli_runner):
     @cli.handle_errors()
     def inner():
-        raise exceptions.VaultException("yay")
+        raise exceptions.VaultException("foo") from ValueError("bar")
 
-    with pytest.raises(click.ClickException):
+    with pytest.raises(click.ClickException) as exc_info:
         inner()
 
+    assert str(exc_info.value) == "VaultException: foo\nValueError: bar"
+
 
 def test_version(cli_runner):
     result = cli_runner.invoke(cli.cli, ["--version"])
diff --git a/tests/unit/test_ssh.py b/tests/unit/test_ssh.py
index 1674b39..bdd3986 100644
--- a/tests/unit/test_ssh.py
+++ b/tests/unit/test_ssh.py
@@ -1,6 +1,6 @@
 import pytest
 
-from vault_cli import ssh
+from vault_cli import exceptions, ssh
 
 
 @pytest.fixture
@@ -24,10 +24,22 @@ def test_ensure_agent_already_present(exec, environ):
 # This should rather be an integration test
 def test_launch_command(environ):
     environ.update({"A": "B"})
-    stdout = ssh._launch_command(["env"], "", {"C": "D"})
+    stdout = ssh._launch_command(command=["env"], stdin="", environment={"C": "D"})
     assert "A=B\nC=D" in stdout
 
 
+# This should rather be an integration test
+def test_launch_command_error(environ):
+    non_existing_filename = "a" * 30
+    with pytest.raises(exceptions.VaultSubprocessException) as exc_info:
+        ssh._launch_command(
+            command=["cat", non_existing_filename], stdin="", environment={}
+        )
+
+    error_message = f"cat: {non_existing_filename}: No such file or directory"
+    assert str(exc_info.value) == error_message
+
+
 @pytest.fixture
 def launch_command(mocker):
     return mocker.patch("vault_cli.ssh._launch_command")

pip install -e .[dev,test,lint,docs]
parse_log_pytest
requirements.txt
pytest
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
babel==2.17.0
backports.tarfile==1.2.0
black==25.1.0
cachetools==5.5.2
certifi==2025.1.31
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.6
coverage==7.8.0
cryptography==44.0.2
distlib==0.3.9
doc8==1.1.2
docutils==0.21.2
exceptiongroup==1.2.2
filelock==3.18.0
flake8==7.2.0
hvac==2.3.0
id==1.5.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
isort==6.0.1
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
jeepney==0.9.0
Jinja2==3.1.6
keyring==25.6.0
markdown-it-py==3.0.0
MarkupSafe==3.0.2
mccabe==0.7.0
mdurl==0.1.2
more-itertools==10.6.0
mypy==1.15.0
mypy-extensions==1.0.0
nh3==0.2.21
packaging==24.2
pathspec==0.12.1
pbr==6.1.1
platformdirs==4.3.7
pluggy==1.5.0
pycodestyle==2.13.0
pycparser==2.22
pyflakes==3.3.1
Pygments==2.19.1
pyproject-api==1.9.0
pytest==8.3.5
pytest-click==1.1.0
pytest-cov==6.0.0
pytest-mock==3.14.0
PyYAML==6.0.2
readme_renderer==44.0
requests==2.32.3
requests-mock==1.12.1
requests-toolbelt==1.0.0
restructuredtext_lint==1.4.0
rfc3986==2.0.0
rich==14.0.0
SecretStorage==3.3.3
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-autodoc-typehints==2.3.0
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-programoutput==0.18
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
stevedore==5.4.1
tomli==2.2.1
tox==4.25.0
twine==6.1.0
typing_extensions==4.13.0
urllib3==2.3.0
-e git+https://github.com/peopledoc/vault-cli.git@27f8c125ae820730940230b10aa520fd7b3afa94#egg=vault_cli
virtualenv==20.29.3
zipp==3.21.0

name: vault-cli
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - babel==2.17.0
      - backports-tarfile==1.2.0
      - black==25.1.0
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cffi==1.17.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - colorama==0.4.6
      - coverage==7.8.0
      - cryptography==44.0.2
      - distlib==0.3.9
      - doc8==1.1.2
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==7.2.0
      - hvac==2.3.0
      - id==1.5.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - isort==6.0.1
      - jaraco-classes==3.4.0
      - jaraco-context==6.0.1
      - jaraco-functools==4.1.0
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==25.6.0
      - markdown-it-py==3.0.0
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mdurl==0.1.2
      - more-itertools==10.6.0
      - mypy==1.15.0
      - mypy-extensions==1.0.0
      - nh3==0.2.21
      - packaging==24.2
      - pathspec==0.12.1
      - pbr==6.1.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - pytest-click==1.1.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pyyaml==6.0.2
      - readme-renderer==44.0
      - requests==2.32.3
      - requests-mock==1.12.1
      - requests-toolbelt==1.0.0
      - restructuredtext-lint==1.4.0
      - rfc3986==2.0.0
      - rich==14.0.0
      - secretstorage==3.3.3
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-autodoc-typehints==2.3.0
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-programoutput==0.18
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - stevedore==5.4.1
      - tomli==2.2.1
      - tox==4.25.0
      - twine==6.1.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - zipp==3.21.0
prefix: /opt/conda/envs/vault-cli

tests/unit/test_cli.py::test_handle_errors tests/unit/test_ssh.py::test_launch_command_error
tests/integration/test_integration.py::test_env_var_config
tests/unit/test_cli.py::test_options tests/unit/test_cli.py::test_list tests/unit/test_cli.py::test_get_text[extra_args0] tests/unit/test_cli.py::test_get_text[extra_args1] tests/unit/test_cli.py::test_get_text_special_cases[value0----\nlist:\n- tests/unit/test_cli.py::test_get_text_special_cases[value1----\na: tests/unit/test_cli.py::test_get_text_special_cases[None-null\n] tests/unit/test_cli.py::test_get_yaml tests/unit/test_cli.py::test_get_all_no_flat tests/unit/test_cli.py::test_get_all_flat tests/unit/test_cli.py::test_set tests/unit/test_cli.py::test_set_without_value tests/unit/test_cli.py::test_set_arg_stdin tests/unit/test_cli.py::test_set_stdin tests/unit/test_cli.py::test_set_with_both_prompt_and_stdin tests/unit/test_cli.py::test_set_with_both_yaml_and_multiple_values tests/unit/test_cli.py::test_set_prompt tests/unit/test_cli.py::test_set_yaml tests/unit/test_cli.py::test_set_overwrite_valid[args0-b] tests/unit/test_cli.py::test_set_overwrite_valid[args1-b] tests/unit/test_cli.py::test_set_overwrite_valid[args2-b] tests/unit/test_cli.py::test_set_overwrite_valid[args3-c] tests/unit/test_cli.py::test_set_overwrite_safe_invalid[args0] tests/unit/test_cli.py::test_set_overwrite_safe_invalid[args1] tests/unit/test_cli.py::test_set_mix_secrets_folders tests/unit/test_cli.py::test_set_mix_folders_secrets tests/unit/test_cli.py::test_delete tests/unit/test_cli.py::test_env tests/unit/test_cli.py::test_env_prefix tests/unit/test_cli.py::test_env_filter_key tests/unit/test_cli.py::test_env_omit_single_key tests/unit/test_cli.py::test_main tests/unit/test_cli.py::test_main_askpass tests/unit/test_cli.py::test_load_config_no_config tests/unit/test_cli.py::test_load_config[bla-expected0] tests/unit/test_cli.py::test_load_config[None-expected1] tests/unit/test_cli.py::test_extract_special_args[config0-environ0-expected0] tests/unit/test_cli.py::test_extract_special_args[config1-environ1-expected1] tests/unit/test_cli.py::test_extract_special_args[config2-environ2-expected2] tests/unit/test_cli.py::test_extract_special_args[config3-environ3-expected3] tests/unit/test_cli.py::test_extract_special_args[config4-environ4-expected4] tests/unit/test_cli.py::test_extract_special_args[config5-environ5-expected5] tests/unit/test_cli.py::test_extract_special_args[config6-environ6-expected6] tests/unit/test_cli.py::test_extract_special_args[config7-environ7-expected7] tests/unit/test_cli.py::test_set_verbosity tests/unit/test_cli.py::test_dump_config tests/unit/test_cli.py::test_delete_all tests/unit/test_cli.py::test_delete_all_cancel tests/unit/test_cli.py::test_delete_all_force tests/unit/test_cli.py::test_mv tests/unit/test_cli.py::test_mv_overwrite_safe tests/unit/test_cli.py::test_mv_overwrite_force tests/unit/test_cli.py::test_mv_mix_folders_secrets tests/unit/test_cli.py::test_mv_mix_secrets_folders tests/unit/test_cli.py::test_template_from_stdin tests/unit/test_cli.py::test_template_from_file tests/unit/test_cli.py::test_template_from_file_with_include tests/unit/test_cli.py::test_lookup_token tests/unit/test_cli.py::test_version tests/unit/test_cli.py::test_ssh tests/unit/test_cli.py::test_ssh_passphrase tests/unit/test_cli.py::test_ssh_wrong_format_key tests/unit/test_cli.py::test_ssh_wrong_format_passphrase tests/unit/test_cli.py::test_parse_octal[None-None] tests/unit/test_cli.py::test_parse_octal[022-18] tests/unit/test_cli.py::test_parse_octal[0o123-83] tests/unit/test_cli.py::test_repr_octal[None-None] tests/unit/test_cli.py::test_repr_octal[18-0o022] tests/unit/test_cli.py::test_repr_octal[83-0o123] tests/unit/test_cli.py::test_repr_octal[5349-0o12345] tests/unit/test_cli.py::test_repr_octal[0-0o000] tests/unit/test_ssh.py::test_ensure_agent tests/unit/test_ssh.py::test_ensure_agent_already_present tests/unit/test_ssh.py::test_launch_command tests/unit/test_ssh.py::test_add_key tests/unit/test_ssh.py::test_add_key_password
vault_cli/ssh.py CHANGELOG.rst vault_cli/exceptions.py vault_cli/cli.py
vault_cli/ssh.py CHANGELOG.rst vault_cli/exceptions.py vault_cli/cli.py