hermes - ✅(Solved) Fix [Bug]: Environment Variables Not Passed Through to SSH Sessions [4 pull requests, 3 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#14091Fetched 2026-04-23 07:46:49
View on GitHub
Comments
3
Participants
2
Timeline
12
Reactions
0
Timeline (top)
labeled ×5cross-referenced ×4commented ×3

Error Message

Additional Logs / Traceback (optional)

Root Cause

Root Cause Analysis (optional)

Fix Action

Workaround

Currently, environment variables can be made available by:

  1. Using a .env file in the user's home directory and sourcing it in code:

    from dotenv import load_dotenv
    load_dotenv("/home/hermes/.env")
  2. Hardcoding credentials in the session (not recommended for production)

  3. Manual export before running commands (not persistent across sessions)

PR fix notes

PR #14107: fix(ssh): forward skill env vars into SSH snapshots

Description (problem / solution / changelog)

Summary

  • forward skill-registered passthrough vars with SendEnv during SSH login-shell bootstrap
  • keep forwarding scoped to init_session() so later SSH commands reuse the captured snapshot
  • add regression tests covering registered vs unregistered env vars

Root cause

  • the SSH backend captured its login-shell snapshot without any SendEnv options
  • skill passthrough vars existed in the Hermes process, but OpenSSH was never told to forward them into the remote login shell

Fix

  • build SendEnv args from the registered passthrough allowlist for env vars that are actually present locally
  • attach those args only to login-shell SSH invocations so the snapshot persists them for later terminal / execute_code calls

Regression coverage

  • added tests/tools/test_ssh_env_passthrough.py to assert registered vars are forwarded during login-shell bootstrap
  • added a negative test proving unregistered vars are not forwarded

Testing

  • scripts/run_tests.sh tests/tools/test_ssh_env_passthrough.py
  • scripts/run_tests.sh tests/tools/test_sync_back_backends.py tests/tools/test_skill_env_passthrough.py

Closes #14091

Changed files

  • tests/tools/test_ssh_env_passthrough.py (added, +68/-0)
  • tools/environments/ssh.py (modified, +20/-1)

PR #14126: fix(ssh): pass through skill env vars to remote shells

Description (problem / solution / changelog)

Summary

  • inject allowlisted env passthrough variables into SSH-backed shell sessions before the remote bash command runs
  • load passthrough values from the live process env first, then fall back to ~/.hermes/.env for persisted skill secrets
  • keep Hermes-managed provider credentials blocked so SSH passthrough matches the existing sandbox safety rules

Testing

  • pytest -o addopts= tests/tools/test_ssh_environment.py -k "passthrough_env_vars or test_base_flags or test_user_host_suffix"
  • pytest -o addopts= tests/tools/test_ssh_environment.py

Closes #14091

Changed files

  • tests/tools/test_ssh_environment.py (modified, +31/-0)
  • tools/environments/ssh.py (modified, +39/-0)

PR #14354: fix(ssh): forward skill-allowlisted env vars over SSH via SendEnv

Description (problem / solution / changelog)

Fixes #14091

The bug

tools/env_passthrough.py already builds an allowlist of env vars that should reach sandboxed environments — populated from skill required_environment_variables frontmatter and terminal.env_passthrough config. The local and code_execution backends consult is_env_passthrough() / get_all_passthrough(), but SSHEnvironment never did.

Result: when terminal_backend: ssh, every ssh ... bash -c subprocess inherits a stripped child environment that excludes the allowlist, and the remote bash session sees no skill-declared variables — even with AcceptEnv * configured on the remote sshd.

The reporter's diagnosis is correct: the SSH command was missing -o SendEnv=NAME.

Fix

In _build_ssh_command():

  • Append -o SendEnv=<NAME> for every var in get_all_passthrough() that is actually present in os.environ.
  • Names are sorted for deterministic command construction (so ControlMaster connection reuse stays stable).

In _run_bash():

  • -o SendEnv only forwards names; the OpenSSH client reads values from its own process environment. Pass them explicitly via a new _build_subprocess_env() so allowlisted vars are guaranteed to be present even if a future caller scrubs the parent env.

Failure to import or call get_all_passthrough() is non-fatal — SSH still works, just without forwarding (matches the existing best-effort posture in skills_tool.py).

Why this is the right layer

  • Same allowlist source as local / code_execution — single source of truth (get_all_passthrough()), so security guarantees stay consistent (Hermes provider credentials are still blocked from skill registration per GHSA-rhgp-j443-p4rf).
  • Doesn't bypass remote sshd policy — admins still need AcceptEnv on the remote (the issue notes AcceptEnv *); we just stop silently dropping the names client-side.
  • Zero behavior change when no skill/config registers anything — empty allowlist ⇒ no SendEnv flags, no env override.

Verification

uv run --frozen --python 3.11 --extra dev pytest -o addopts='' \
  tests/tools/test_ssh_environment.py \
  tests/tools/test_ssh_bulk_upload.py \
  tests/tools/test_sync_back_backends.py -q
52 passed, 11 skipped

7 new targeted tests in TestBuildSSHCommand:

  • test_no_send_env_when_no_passthrough_registered — zero behavior change when allowlist empty
  • test_send_env_added_for_registered_passthrough_var — the actual #14091 case
  • test_send_env_skips_unset_vars — allowlisted-but-unset vars don't leak as empty SendEnv lines
  • test_send_env_is_deterministic — sorted order for ControlMaster reuse stability
  • test_passthrough_failure_is_non_fatal — SSH keeps working if env_passthrough breaks
  • test_subprocess_env_includes_passthrough_values — values propagated to ssh client process env
  • test_subprocess_env_is_none_when_no_passthrough — don't override default child-env semantics unnecessarily

Notes

  • Minimal, focused diff. Uses the existing env_passthrough infrastructure rather than introducing a parallel mechanism.
  • Doesn't touch skills_tool.py's setup_needed reporting — that's accurate for the registration side; the bug was purely on the SSH consumer side.
  • No new dependencies.

Changed files

  • tests/tools/test_ssh_environment.py (modified, +83/-0)
  • tools/environments/ssh.py (modified, +47/-1)

PR #14414: fix(ssh): send skill env passthrough variables

Description (problem / solution / changelog)

Summary

Fixes #14091.

This makes the SSH terminal backend forward skill/config allowlisted environment variables into remote SSH sessions via OpenSSH SendEnv.

Root cause

Skill readiness can register required_environment_variables through tools.env_passthrough, and local/Docker/code-execution paths already consult that allowlist. The SSH backend, however, only launched ssh ... bash -c ... and never asked OpenSSH to send the allowlisted variables, so remote terminal / execute_code sessions could not see them even when the local Hermes process or ~/.hermes/.env had the values.

Fix

  • Resolve the current passthrough allowlist for each SSH shell invocation.
  • Filter out Hermes provider credentials using the existing provider credential blocklist.
  • Populate the child ssh process environment from os.environ, falling back to Hermes .env values.
  • Add -o SendEnv=<KEY> only for allowlisted keys that have values.
  • Add regressions for explicit SendEnv flags, provider-key filtering, and .env fallback values.

This intentionally uses SendEnv instead of embedding secret values into the remote shell command string; SSH servers still need the corresponding AcceptEnv configuration, matching the issue reproduction.

Validation

  • /Users/stephenyu/Documents/hermes-agent/.venv/bin/python -m pytest tests/tools/test_ssh_environment.py::TestBuildSSHCommand::test_send_env_flags_are_added_when_requested tests/tools/test_ssh_environment.py::TestBuildSSHCommand::test_run_bash_forwards_allowed_env_without_provider_keys tests/tools/test_ssh_environment.py::TestBuildSSHCommand::test_run_bash_uses_hermes_env_file_values_for_send_env -q --tb=short
  • /Users/stephenyu/Documents/hermes-agent/.venv/bin/python -m pytest tests/tools/test_ssh_environment.py tests/tools/test_env_passthrough.py -q --tb=short
  • git diff --check

Note: the full SSH test file still emits pre-existing PytestUnhandledThreadExceptionWarning warnings from its mocked stdout object in unrelated constructor tests; the new targeted tests avoid that warning and pass cleanly.

Changed files

  • tests/tools/test_ssh_environment.py (modified, +75/-0)
  • tools/environments/ssh.py (modified, +50/-3)

Code Example

required_environment_variables:
     - name: NEXTCLOUD_URL
       default: https://next.swokiz.com
     - name: NEXTCLOUD_USER
       prompt: Nextcloud username
     - name: NEXTCLOUD_PASS
       prompt: Nextcloud password

---

export NEXTCLOUD_URL="https://next.swokiz.com"
   export NEXTCLOUD_USER="testuser"
   export NEXTCLOUD_PASS="testpass"

---

# /etc/ssh/sshd_config
   AcceptEnv *

---

sudo systemctl restart sshd
   # Start new Hermes session

---

# In terminal or execute_code
   import os
   print(os.environ.get("NEXTCLOUD_URL"))  # Returns None

---

$ echo $NEXTCLOUD_URL
https://next.swokiz.com

---

>>> import os
>>> os.environ.get("NEXTCLOUD_URL")
'https://next.xxx.com'

---

from dotenv import load_dotenv
   load_dotenv("/home/hermes/.env")

---

agent.log:

2026-04-22 17:48:22,153 WARNING gateway.run: No user allowlists configured. All unauthorized users will be denied. Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id).
2026-04-22 17:48:22,155 INFO gateway.run: Previous gateway exited cleanly — skipping session suspension
2026-04-22 17:48:22,201 INFO gateway.run: Connecting to api_server...
2026-04-22 17:48:22,203 INFO gateway.platforms.api_server: [Api_Server] API server listening on http://0.0.0.0:8642 (model: hermes-agent)
2026-04-22 17:48:22,204 INFO gateway.run: ✓ api_server connected
2026-04-22 17:48:22,204 INFO gateway.run: 1 hook(s) loaded
2026-04-22 17:48:22,204 INFO gateway.run: Gateway running with 1 platform(s)
2026-04-22 17:48:22,206 INFO gateway.run: Channel directory built: 0 target(s)
2026-04-22 17:48:22,206 INFO gateway.run: Press Ctrl+C to stop
2026-04-22 17:48:22,207 INFO gateway.run: Cron ticker started (interval=60s)
2026-04-22 17:48:54,899 INFO run_agent: Loaded environment variables from /opt/data/.env
2026-04-22 17:48:55,410 DEBUG firecrawl: Debugging logger setup
2026-04-22 17:48:55,730 DEBUG tools.mcp_tool: No MCP servers configured
2026-04-22 17:48:56,219 DEBUG agent.auxiliary_client: Auxiliary client: custom endpoint (hermes_agent_router, api_mode=chat_completions)
2026-04-22 17:48:56,245 INFO agent.auxiliary_client: Vision auto-detect: using active provider custom (hermes_agent_router)
2026-04-22 17:48:56,285 DEBUG plugins.memory: Memory provider 'builtin' not found in bundled or user plugins
2026-04-22 17:48:56,314 INFO agent.model_metadata: Could not detect context length for model 'hermes_agent_router' at https://litellm.xx.xx.com/v1 — defaulting to 128,000 tokens (probe-down). Set model.context_length in config.yaml to override.
2026-04-22 17:48:56,354 INFO agent.auxiliary_client: Auxiliary auto-detect: using main provider custom (hermes_agent_router)

---
RAW_BUFFERClick to expand / collapse

Bug Description

Environment variables declared in a skill's required_environment_variables frontmatter are not being passed through to SSH terminal sessions, even when:

  • The variables exist in the Docker container environment
  • The variables are set in the Hermes .env file
  • The SSH daemon is configured with AcceptEnv *
  • A new session is created

The skill's passthrough mechanism reports setup_needed: false and missing_required_environment_variables: [], indicating the variables should be available. However, they are not present in either terminal or execute_code environments within the SSH session.

Impact: Skills that depend on environment variables (e.g., nextcloud-integration, gif-search, webhook-subscriptions) cannot function automatically and require manual workarounds like sourcing .env files or hardcoding credentials.

Steps to Reproduce

  1. Create a skill with required_environment_variables in SKILL.md frontmatter:

    required_environment_variables:
      - name: NEXTCLOUD_URL
        default: https://next.swokiz.com
      - name: NEXTCLOUD_USER
        prompt: Nextcloud username
      - name: NEXTCLOUD_PASS
        prompt: Nextcloud password
  2. Set the environment variables in the Docker container and/or host:

    export NEXTCLOUD_URL="https://next.swokiz.com"
    export NEXTCLOUD_USER="testuser"
    export NEXTCLOUD_PASS="testpass"
  3. Configure SSH daemon to accept the variables:

    # /etc/ssh/sshd_config
    AcceptEnv *
  4. Restart sshd and create a new Hermes session:

    sudo systemctl restart sshd
    # Start new Hermes session
  5. Load the skill and check for environment variables:

    # In terminal or execute_code
    import os
    print(os.environ.get("NEXTCLOUD_URL"))  # Returns None
  6. Observe that variables are not present despite skill reporting setup_needed: false

Expected Behavior

When a skill declares required_environment_variables and those variables exist in the Hermes backend environment:

  1. Variables should be passed through to all tool backends including SSH sessions
  2. execute_code should have access to the variables via os.environ
  3. terminal commands should have access to the variables in the shell environment
  4. Skill readiness should accurately reflect whether variables are actually accessible (not just whether they're declared)

Example expected output:

$ echo $NEXTCLOUD_URL
https://next.swokiz.com
>>> import os
>>> os.environ.get("NEXTCLOUD_URL")
'https://next.xxx.com'

Actual Behavior

  • Environment variable passthrough mechanism for skills (feature introduction)
  • SSH session environment inheritance (if such an issue exists)
  • Skill readiness reporting accuracy (setup_needed false positives)

Workaround

Currently, environment variables can be made available by:

  1. Using a .env file in the user's home directory and sourcing it in code:

    from dotenv import load_dotenv
    load_dotenv("/home/hermes/.env")
  2. Hardcoding credentials in the session (not recommended for production)

  3. Manual export before running commands (not persistent across sessions)

Affected Component

Skills (skill loading, skill hub, skill guard), Tools (terminal, file ops, web, code execution, etc.)

Messaging Platform (if gateway-related)

N/A (CLI only)

Debug Report

agent.log:

2026-04-22 17:48:22,153 WARNING gateway.run: No user allowlists configured. All unauthorized users will be denied. Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id).
2026-04-22 17:48:22,155 INFO gateway.run: Previous gateway exited cleanly — skipping session suspension
2026-04-22 17:48:22,201 INFO gateway.run: Connecting to api_server...
2026-04-22 17:48:22,203 INFO gateway.platforms.api_server: [Api_Server] API server listening on http://0.0.0.0:8642 (model: hermes-agent)
2026-04-22 17:48:22,204 INFO gateway.run: ✓ api_server connected
2026-04-22 17:48:22,204 INFO gateway.run: 1 hook(s) loaded
2026-04-22 17:48:22,204 INFO gateway.run: Gateway running with 1 platform(s)
2026-04-22 17:48:22,206 INFO gateway.run: Channel directory built: 0 target(s)
2026-04-22 17:48:22,206 INFO gateway.run: Press Ctrl+C to stop
2026-04-22 17:48:22,207 INFO gateway.run: Cron ticker started (interval=60s)
2026-04-22 17:48:54,899 INFO run_agent: Loaded environment variables from /opt/data/.env
2026-04-22 17:48:55,410 DEBUG firecrawl: Debugging logger setup
2026-04-22 17:48:55,730 DEBUG tools.mcp_tool: No MCP servers configured
2026-04-22 17:48:56,219 DEBUG agent.auxiliary_client: Auxiliary client: custom endpoint (hermes_agent_router, api_mode=chat_completions)
2026-04-22 17:48:56,245 INFO agent.auxiliary_client: Vision auto-detect: using active provider custom (hermes_agent_router)
2026-04-22 17:48:56,285 DEBUG plugins.memory: Memory provider 'builtin' not found in bundled or user plugins
2026-04-22 17:48:56,314 INFO agent.model_metadata: Could not detect context length for model 'hermes_agent_router' at https://litellm.xx.xx.com/v1 — defaulting to 128,000 tokens (probe-down). Set model.context_length in config.yaml to override.
2026-04-22 17:48:56,354 INFO agent.auxiliary_client: Auxiliary auto-detect: using main provider custom (hermes_agent_router)

Operating System

Ubuntu 24.04

Python Version

3.13.5

Hermes Version

v0.10.0 (2026.4.16)

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

No response

Proposed Fix (optional)

Maybe ssh command here need to be extended. cmd.extend(["-o", f"ControlPath={self.control_socket}"])

Missing: SSH command should include -o SendEnv="VAR_NAME"

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

extent analysis

TL;DR

The environment variables declared in a skill's required_environment_variables frontmatter are not being passed through to SSH terminal sessions, and a potential fix involves modifying the SSH command to include the -o SendEnv option.

Guidance

  1. Verify SSH configuration: Ensure that the SSH daemon is configured to accept environment variables by checking the AcceptEnv directive in the sshd_config file.
  2. Modify SSH command: Extend the SSH command to include the -o SendEnv option, allowing environment variables to be sent to the SSH session, as suggested in the proposed fix.
  3. Test environment variables: After modifying the SSH command, test whether the environment variables are being passed through to the SSH session by checking their presence in the terminal or execute_code environments.

Example

# In ssh.py, modify the cmd list to include the SendEnv option
cmd.extend(["-o", f"SendEnv={variable_name}"])

Notes

The proposed fix suggests modifying the SSH command to include the -o SendEnv option, but the exact implementation details are not provided. Additionally, the variable_name in the example code snippet should be replaced with the actual name of the environment variable being passed.

Recommendation

Apply the workaround by modifying the SSH command to include the -o SendEnv option, as this is the most direct approach to resolving the issue. This change will allow environment variables to be sent to the SSH session, addressing the problem of missing environment variables in the terminal and execute_code environments.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Bug]: Environment Variables Not Passed Through to SSH Sessions [4 pull requests, 3 comments, 2 participants]