hermes - ✅(Solved) Fix [Bug]: TTS tools fail to read API keys from ~/.hermes/.env — os.getenv() doesn't see dotenv values [4 pull requests, 1 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#17140Fetched 2026-04-29 06:37:06
View on GitHub
Comments
1
Participants
2
Timeline
13
Reactions
0
Timeline (top)
labeled ×5cross-referenced ×4referenced ×3commented ×1

Error Message

try: from hermes_cli.config import get_env_value except Exception: _get_env_value_fallback = os.getenv def get_env_value(key, default=None): return _get_env_value_fallback(key) or default

Root Cause

hermes_cli.config uses a custom .env parser (get_env_value()) that reads ~/.hermes/.env directly. This is distinct from python-dotenv — the .env file is NOT automatically loaded into os.environ by the hermes startup sequence. Tools that call os.getenv() directly find empty values, while tools that use get_env_value() find the actual keys.

This is the same class of bug documented in issue #14693 (tool-layer env vars defeating config routing), just applied to API keys rather than model names.

Fix Action

Fixed

PR fix notes

PR #3: fix: resolve 7 identified issues [automated]

Description (problem / solution / changelog)

Summary

This PR resolves 7 issues identified in the NousResearch/hermes-agent upstream repository.

Issues Fixed

#17139 - Cron Telegram deliver=['telegram'] fails

File: cron/scheduler.py Problem: When deliver is specified as a list (e.g., ['telegram']), the scheduler used str(deliver).split(",") which converted it to "['telegram']" instead of parsing the list items. Fix: Added check isinstance(deliver, list) to properly handle list-format delivery targets.

#17086 - Custom endpoint URL rewrite strips /anthropic path

File: agent/auxiliary_client.py Problem: Custom endpoints with api_mode: anthropic_messages had URLs rewritten from /anthropic to /v1, causing 404 errors. Fix: Added api_mode != "anthropic_messages" guard to skip URL rewriting when using Anthropic-native message format.

#17054 - Slack manifest broken metadata

File: hermes_cli/slack_cli.py Problem: Slack manifest used "major_version": 1, "minor_version": 1 which is invalid for v1 manifests. Fix: Changed to use "version": "2.0.0" format.

#17049 - Windows wmic UnicodeDecodeError

File: hermes_cli/gateway.py Problem: wmic command output on Windows uses system code page, not UTF-8, causing UnicodeDecodeError and cascading AttributeError on result.stdout. Fix: Added encoding="utf-8", errors="replace" to subprocess call and OSError exception handling.

#17043 - TelegramFallbackTransport DoH drops IPs

File: gateway/platforms/telegram_network.py Problem: discover_fallback_ips() filtered out DoH-resolved IPs that matched system DNS, causing fallback to hardcoded seed IPs on networks where seed IPs aren't routable. Fix: Removed ip not in system_ips exclusion - DoH-confirmed IPs are now preserved.

#17140 - TTS tools use os.getenv() instead of get_env_value()

File: tools/tts_tool.py Problem: TTS providers (elevenlabs, minimax, mistral, xai, gemini) used os.getenv() which doesn't read values from ~/.hermes/.env loaded via the hermes config bridge. Fix: Replaced with get_env_value() calls for all TTS API keys.

hermes_cli/config.py - _sanitize_env_lines KEY= detection

File: hermes_cli/config.py Problem: _sanitize_env_lines matched KEY= patterns that appeared as substrings within other keys (e.g., LM_API_KEY matched inside GLM_API_KEY). Fix: Only treat KEY= as a separator when it appears at position 0 or after a non-identifier character.

Files Modified

  • agent/auxiliary_client.py (+289 lines)
  • cron/scheduler.py (+21 lines)
  • gateway/platforms/telegram_network.py (+9 lines)
  • hermes_cli/config.py (+259 lines)
  • hermes_cli/gateway.py (+21 lines)
  • hermes_cli/slack_cli.py (+3 lines)
  • tools/tts_tool.py (+43 lines)

Testing

All fixes are focused on bug corrections with minimal behavioral changes. The existing test suite should pass.


Automated PR created by Hermes Agent cron job

Changed files

  • .gitignore (modified, +1/-0)
  • Dockerfile (modified, +8/-3)
  • agent/auxiliary_client.py (modified, +344/-55)
  • agent/context_compressor.py (modified, +509/-54)
  • agent/credential_pool.py (modified, +6/-3)
  • agent/error_classifier.py (modified, +31/-0)
  • agent/image_routing.py (added, +236/-0)
  • agent/memory_manager.py (modified, +114/-5)
  • agent/model_metadata.py (modified, +51/-21)
  • agent/nous_rate_guard.py (modified, +142/-0)
  • agent/onboarding.py (added, +191/-0)
  • agent/prompt_builder.py (modified, +34/-0)
  • agent/shell_hooks.py (modified, +5/-1)
  • agent/skill_commands.py (modified, +2/-2)
  • agent/title_generator.py (modified, +33/-4)
  • agent/transports/chat_completions.py (modified, +11/-0)
  • cli-config.yaml.example (modified, +28/-8)
  • cli.py (modified, +466/-196)
  • cron/jobs.py (modified, +31/-3)
  • cron/scheduler.py (modified, +44/-6)
  • docker/entrypoint.sh (modified, +9/-7)
  • flake.nix (modified, +1/-0)
  • gateway/channel_directory.py (modified, +67/-14)
  • gateway/config.py (modified, +68/-2)
  • gateway/display_config.py (modified, +3/-1)
  • gateway/mirror.py (modified, +57/-11)
  • gateway/platforms/__init__.py (modified, +2/-0)
  • gateway/platforms/base.py (modified, +152/-5)
  • gateway/platforms/discord.py (modified, +24/-28)
  • gateway/platforms/email.py (modified, +3/-0)
  • gateway/platforms/feishu.py (modified, +26/-7)
  • gateway/platforms/helpers.py (modified, +9/-0)
  • gateway/platforms/matrix.py (modified, +73/-3)
  • gateway/platforms/slack.py (modified, +753/-70)
  • gateway/platforms/telegram.py (modified, +45/-0)
  • gateway/platforms/telegram_network.py (modified, +7/-2)
  • gateway/platforms/yuanbao.py (added, +4754/-0)
  • gateway/platforms/yuanbao_media.py (added, +647/-0)
  • gateway/platforms/yuanbao_proto.py (added, +1210/-0)
  • gateway/platforms/yuanbao_sticker.py (added, +558/-0)
  • gateway/run.py (modified, +745/-259)
  • gateway/session.py (modified, +11/-2)
  • gateway/status.py (modified, +8/-1)
  • gateway/stream_consumer.py (modified, +110/-0)
  • gateway/whatsapp_identity.py (modified, +21/-1)
  • hermes_cli/auth.py (modified, +28/-3)
  • hermes_cli/backup.py (modified, +177/-1)
  • hermes_cli/commands.py (modified, +113/-5)
  • hermes_cli/config.py (modified, +349/-34)
  • hermes_cli/copilot_auth.py (modified, +1/-1)
  • hermes_cli/debug.py (modified, +11/-5)
  • hermes_cli/doctor.py (modified, +2/-0)
  • hermes_cli/fallback_cmd.py (added, +361/-0)
  • hermes_cli/gateway.py (modified, +35/-10)
  • hermes_cli/main.py (modified, +577/-30)
  • hermes_cli/model_catalog.py (added, +329/-0)
  • hermes_cli/models.py (modified, +98/-6)
  • hermes_cli/nous_subscription.py (modified, +16/-8)
  • hermes_cli/platforms.py (modified, +1/-0)
  • hermes_cli/providers.py (modified, +11/-0)
  • hermes_cli/runtime_provider.py (modified, +31/-0)
  • hermes_cli/setup.py (modified, +69/-14)
  • hermes_cli/skills_hub.py (modified, +230/-20)
  • hermes_cli/slack_cli.py (added, +151/-0)
  • hermes_cli/status.py (modified, +2/-1)
  • hermes_cli/timeouts.py (modified, +4/-4)
  • hermes_cli/tips.py (modified, +2/-3)
  • hermes_cli/tools_config.py (modified, +14/-3)
  • hermes_cli/web_server.py (modified, +8/-10)
  • hermes_logging.py (modified, +3/-4)
  • hermes_state.py (modified, +319/-63)
  • model_tools.py (modified, +44/-13)
  • nix/checks.nix (modified, +30/-3)
  • nix/hermes-agent.nix (added, +186/-0)
  • nix/nixosModules.nix (modified, +81/-6)
  • nix/overlays.nix (added, +10/-0)
  • nix/packages.nix (modified, +6/-107)
  • nix/python.nix (modified, +2/-1)
  • nix/tui.nix (modified, +2/-1)
  • optional-skills/creative/touchdesigner-mcp/SKILL.md (modified, +7/-1)
  • optional-skills/creative/touchdesigner-mcp/references/audio-reactive.md (added, +175/-0)
  • optional-skills/creative/touchdesigner-mcp/references/geometry-comp.md (added, +121/-0)
  • optional-skills/creative/touchdesigner-mcp/references/glsl.md (added, +151/-0)
  • optional-skills/creative/touchdesigner-mcp/references/layout-compositor.md (added, +131/-0)
  • optional-skills/creative/touchdesigner-mcp/references/operator-tips.md (added, +106/-0)
  • optional-skills/creative/touchdesigner-mcp/references/pitfalls.md (modified, +221/-25)
  • optional-skills/creative/touchdesigner-mcp/references/postfx.md (added, +183/-0)
  • optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py (modified, +27/-2)
  • plugins/google_meet/README.md (added, +131/-0)
  • plugins/google_meet/SKILL.md (added, +148/-0)
  • plugins/google_meet/__init__.py (added, +103/-0)
  • plugins/google_meet/audio_bridge.py (added, +244/-0)
  • plugins/google_meet/cli.py (added, +478/-0)
  • plugins/google_meet/meet_bot.py (added, +852/-0)
  • plugins/google_meet/node/__init__.py (added, +54/-0)
  • plugins/google_meet/node/cli.py (added, +125/-0)
  • plugins/google_meet/node/client.py (added, +107/-0)
  • plugins/google_meet/node/protocol.py (added, +124/-0)
  • plugins/google_meet/node/registry.py (added, +112/-0)
  • plugins/google_meet/node/server.py (added, +193/-0)

PR #16165: fix: resolve 7 identified issues [automated]

Description (problem / solution / changelog)

Summary

This PR resolves 7 issues identified in the NousResearch/hermes-agent upstream repository.

Issues Fixed

#17139 - Cron Telegram deliver=['telegram'] fails

File: cron/scheduler.py Problem: When deliver is specified as a list (e.g., ['telegram']), the scheduler used str(deliver).split(",") which converted it to "['telegram']" instead of parsing the list items. Fix: Added check isinstance(deliver, list) to properly handle list-format delivery targets.

#17086 - Custom endpoint URL rewrite strips /anthropic path

File: agent/auxiliary_client.py Problem: Custom endpoints with api_mode: anthropic_messages had URLs rewritten from /anthropic to /v1, causing 404 errors. Fix: Added api_mode != "anthropic_messages" guard to skip URL rewriting when using Anthropic-native message format.

#17054 - Slack manifest broken metadata

File: hermes_cli/slack_cli.py Problem: Slack manifest used "major_version": 1, "minor_version": 1 which is invalid for v1 manifests. Fix: Changed to use "version": "2.0.0" format.

#17049 - Windows wmic UnicodeDecodeError

File: hermes_cli/gateway.py Problem: wmic command output on Windows uses system code page, not UTF-8, causing UnicodeDecodeError and cascading AttributeError on result.stdout. Fix: Added encoding="utf-8", errors="replace" to subprocess call and OSError exception handling.

#17043 - TelegramFallbackTransport DoH drops IPs

File: gateway/platforms/telegram_network.py Problem: discover_fallback_ips() filtered out DoH-resolved IPs that matched system DNS, causing fallback to hardcoded seed IPs on networks where seed IPs aren't routable. Fix: Removed ip not in system_ips exclusion - DoH-confirmed IPs are now preserved.

#17140 - TTS tools use os.getenv() instead of get_env_value()

File: tools/tts_tool.py Problem: TTS providers (elevenlabs, minimax, mistral, xai, gemini) used os.getenv() which doesn't read values from ~/.hermes/.env loaded via the hermes config bridge. Fix: Replaced with get_env_value() calls for all TTS API keys.

hermes_cli/config.py - _sanitize_env_lines KEY= detection

File: hermes_cli/config.py Problem: _sanitize_env_lines matched KEY= patterns that appeared as substrings within other keys (e.g., LM_API_KEY matched inside GLM_API_KEY). Fix: Only treat KEY= as a separator when it appears at position 0 or after a non-identifier character.

Files Modified

  • agent/auxiliary_client.py
  • cron/scheduler.py
  • gateway/platforms/telegram_network.py
  • hermes_cli/config.py
  • hermes_cli/gateway.py
  • hermes_cli/slack_cli.py
  • tools/tts_tool.py

Automated PR updated by Hermes Agent cron job

Changed files

  • .gitignore (modified, +1/-0)
  • Dockerfile (modified, +8/-3)
  • agent/auxiliary_client.py (modified, +344/-55)
  • agent/context_compressor.py (modified, +509/-54)
  • agent/error_classifier.py (modified, +31/-0)
  • agent/image_routing.py (added, +236/-0)
  • agent/memory_manager.py (modified, +114/-5)
  • agent/model_metadata.py (modified, +35/-16)
  • agent/onboarding.py (modified, +52/-5)
  • agent/prompt_builder.py (modified, +34/-0)
  • agent/shell_hooks.py (modified, +5/-1)
  • agent/title_generator.py (modified, +33/-4)
  • agent/transports/chat_completions.py (modified, +11/-0)
  • cli-config.yaml.example (modified, +6/-1)
  • cli.py (modified, +390/-58)
  • cron/jobs.py (modified, +31/-3)
  • cron/scheduler.py (modified, +41/-3)
  • flake.nix (modified, +1/-0)
  • gateway/channel_directory.py (modified, +67/-14)
  • gateway/config.py (modified, +68/-2)
  • gateway/display_config.py (modified, +3/-1)
  • gateway/mirror.py (modified, +57/-11)
  • gateway/platforms/__init__.py (modified, +2/-0)
  • gateway/platforms/base.py (modified, +117/-0)
  • gateway/platforms/discord.py (modified, +24/-23)
  • gateway/platforms/email.py (modified, +3/-0)
  • gateway/platforms/feishu.py (modified, +26/-7)
  • gateway/platforms/helpers.py (modified, +9/-0)
  • gateway/platforms/matrix.py (modified, +73/-3)
  • gateway/platforms/slack.py (modified, +753/-70)
  • gateway/platforms/telegram.py (modified, +45/-0)
  • gateway/platforms/telegram_network.py (modified, +7/-2)
  • gateway/platforms/yuanbao.py (added, +4754/-0)
  • gateway/platforms/yuanbao_media.py (added, +647/-0)
  • gateway/platforms/yuanbao_proto.py (added, +1210/-0)
  • gateway/platforms/yuanbao_sticker.py (added, +558/-0)
  • gateway/run.py (modified, +570/-60)
  • gateway/session.py (modified, +11/-2)
  • gateway/status.py (modified, +8/-1)
  • gateway/stream_consumer.py (modified, +110/-0)
  • gateway/whatsapp_identity.py (modified, +21/-1)
  • hermes_cli/auth.py (modified, +9/-0)
  • hermes_cli/backup.py (modified, +177/-1)
  • hermes_cli/commands.py (modified, +112/-2)
  • hermes_cli/config.py (modified, +358/-34)
  • hermes_cli/copilot_auth.py (modified, +1/-1)
  • hermes_cli/debug.py (modified, +11/-5)
  • hermes_cli/doctor.py (modified, +2/-0)
  • hermes_cli/gateway.py (modified, +35/-10)
  • hermes_cli/main.py (modified, +530/-28)
  • hermes_cli/models.py (modified, +70/-5)
  • hermes_cli/nous_subscription.py (modified, +16/-8)
  • hermes_cli/platforms.py (modified, +1/-0)
  • hermes_cli/providers.py (modified, +11/-0)
  • hermes_cli/runtime_provider.py (modified, +31/-0)
  • hermes_cli/setup.py (modified, +69/-14)
  • hermes_cli/skills_hub.py (modified, +170/-6)
  • hermes_cli/slack_cli.py (added, +151/-0)
  • hermes_cli/status.py (modified, +2/-1)
  • hermes_cli/timeouts.py (modified, +4/-4)
  • hermes_cli/tips.py (modified, +1/-1)
  • hermes_cli/tools_config.py (modified, +14/-3)
  • hermes_cli/web_server.py (modified, +8/-10)
  • hermes_logging.py (modified, +3/-4)
  • hermes_state.py (modified, +307/-62)
  • model_tools.py (modified, +44/-13)
  • nix/checks.nix (modified, +30/-3)
  • nix/hermes-agent.nix (added, +186/-0)
  • nix/nixosModules.nix (modified, +81/-6)
  • nix/overlays.nix (added, +10/-0)
  • nix/packages.nix (modified, +6/-107)
  • nix/python.nix (modified, +2/-1)
  • nix/tui.nix (modified, +2/-1)
  • optional-skills/creative/touchdesigner-mcp/SKILL.md (modified, +7/-1)
  • optional-skills/creative/touchdesigner-mcp/references/audio-reactive.md (added, +175/-0)
  • optional-skills/creative/touchdesigner-mcp/references/geometry-comp.md (added, +121/-0)
  • optional-skills/creative/touchdesigner-mcp/references/glsl.md (added, +151/-0)
  • optional-skills/creative/touchdesigner-mcp/references/layout-compositor.md (added, +131/-0)
  • optional-skills/creative/touchdesigner-mcp/references/operator-tips.md (added, +106/-0)
  • optional-skills/creative/touchdesigner-mcp/references/pitfalls.md (modified, +221/-25)
  • optional-skills/creative/touchdesigner-mcp/references/postfx.md (added, +183/-0)
  • optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py (modified, +27/-2)
  • plugins/google_meet/README.md (added, +131/-0)
  • plugins/google_meet/SKILL.md (added, +148/-0)
  • plugins/google_meet/__init__.py (added, +103/-0)
  • plugins/google_meet/audio_bridge.py (added, +244/-0)
  • plugins/google_meet/cli.py (added, +478/-0)
  • plugins/google_meet/meet_bot.py (added, +852/-0)
  • plugins/google_meet/node/__init__.py (added, +54/-0)
  • plugins/google_meet/node/cli.py (added, +125/-0)
  • plugins/google_meet/node/client.py (added, +107/-0)
  • plugins/google_meet/node/protocol.py (added, +124/-0)
  • plugins/google_meet/node/registry.py (added, +112/-0)
  • plugins/google_meet/node/server.py (added, +193/-0)
  • plugins/google_meet/plugin.yaml (added, +16/-0)
  • plugins/google_meet/process_manager.py (added, +326/-0)
  • plugins/google_meet/realtime/__init__.py (added, +10/-0)
  • plugins/google_meet/realtime/openai_client.py (added, +332/-0)
  • plugins/google_meet/tools.py (added, +348/-0)
  • plugins/memory/hindsight/__init__.py (modified, +154/-43)

PR #17163: fix(tts): resolve API keys from ~/.hermes/.env via get_env_value (#17140)

Description (problem / solution / changelog)

Summary

  • Five TTS provider tools (elevenlabs, xai, minimax, mistral, gemini) called os.getenv("X_API_KEY") directly, ignoring ~/.hermes/.env even though hermes_cli.config.get_env_value() is the canonical resolver elsewhere in the stack.
  • Switch every TTS env-var lookup to get_env_value so the dotenv file works as documented. No behaviour change for users with keys exported in their shell.

The bug

Reproduce per #17140:

  1. Put MINIMAX_API_KEY=sk-... in ~/.hermes/.env (and not in os.environ).
  2. Configure tts.provider: minimax in ~/.hermes/config.yaml.
  3. Run any TTS call (/voice on, text_to_speech tool).
  4. Fails: TTS configuration error (minimax): MINIMAX_API_KEY not set.

hermes_cli/auth.py and agent/credential_pool.py already resolve keys through get_env_value() (lookup order: os.environ first, then ~/.hermes/.env) — see commits 8443998dc / f2d655529. tools/tts_tool.py was left on plain os.getenv and is the only remaining code path that fails when the key is dotenv-only. alt-glitch flagged this as the TTS variant of the same class of bug fixed for auth in #15920.

The fix

tools/tts_tool.py — single new top-level import from hermes_cli.config import get_env_value, then replace every TTS-related os.getenv("...") call with get_env_value("..."):

ProviderLookups migrated
ElevenLabsELEVENLABS_API_KEY (sync + streaming + diagnostics)
xAIXAI_API_KEY, XAI_BASE_URL
MiniMaxMINIMAX_API_KEY (gen + diagnostics + check_tts_requirements)
MistralMISTRAL_API_KEY (gen + check_tts_requirements)
GeminiGEMINI_API_KEY, GOOGLE_API_KEY, GEMINI_BASE_URL

Lookup order is preserved exactly: Gemini still consults GEMINI_API_KEY before GOOGLE_API_KEY, xAI/Gemini base URLs still defer to user config before the env override, and os.getenv("X", "") defaults are preserved as (get_env_value("X") or ""). Other env reads in the file (os.path.*, os.remove, etc.) are untouched.

Test plan

New file tests/tools/test_tts_dotenv_fallback.py, 7 cases:

  • Per-provider regression — each of the 5 backends reads the dotenv key when os.environ is empty (test_elevenlabs_reads_dotenv_key, test_xai_reads_dotenv_key, test_minimax_reads_dotenv_key, test_mistral_reads_dotenv_key, test_gemini_reads_dotenv_key).
  • End-to-end repro — test_minimax_missing_when_only_in_dotenv_before_fix patches hermes_cli.config.load_env to simulate ~/.hermes/.env carrying MINIMAX_API_KEY while os.environ does not, and confirms the production code now succeeds and forwards the key as Authorization: Bearer <key>.
  • check_tts_requirements gate — test_check_tts_requirements_sees_dotenv_minimax confirms the gate that decides whether /voice on is offered also consults the dotenv file.
  • Regression guard — stashing tools/tts_tool.py back to the os.getenv form makes all 7 tests fail with MINIMAX_API_KEY not set / equivalent. Restored after verification.
  • Adjacent suite — tests/tools/test_tts_mistral.py, test_tts_speed.py, test_tts_gemini.py, test_tts_max_text_length.py all pass (70 cases). test_tts_kittentts.py errors on import numpy on origin/main too — pre-existing baseline, unrelated to this diff.

Run:

uv run --with pytest --with pytest-xdist python3 -m pytest tests/tools/test_tts_dotenv_fallback.py tests/tools/test_tts_mistral.py tests/tools/test_tts_speed.py tests/tools/test_tts_gemini.py tests/tools/test_tts_max_text_length.py
# 77 passed, 1 baseline error in test_tts_kittentts (numpy missing on main)

Related

  • Fixes #17140
  • Same class of bug as #15914 / #15920 (auth.py + agent/credential_pool variants — already on main via 8443998dc).

Changed files

  • tests/tools/test_tts_dotenv_fallback.py (added, +233/-0)
  • tools/tts_tool.py (modified, +20/-15)

PR #17262: fix(tts): use get_env_value for API key resolution from ~/.hermes/.env

Description (problem / solution / changelog)

Summary

Fixes #17140 — TTS provider tools fail to read API keys from ~/.hermes/.env.

Problem

TTS provider tools (elevenlabs, minimax, mistral, xai, gemini) use os.getenv("X_API_KEY") directly, which does not read values stored in ~/.hermes/.env. The hermes config system uses get_env_value() from hermes_cli.config which reads .env directly, but the TTS tools bypass this, causing "API key not set" errors even when keys are correctly configured.

Solution

Replace all os.getenv() calls for API keys and base URLs in tools/tts_tool.py with _get_env_value() (aliased from hermes_cli.config.get_env_value), following the same pattern already used by hermes_cli/auth.py and hermes_cli/gateway.py.

Changes

Added _get_env_value import with fallback (lines 53-59):

try:
    from hermes_cli.config import get_env_value as _get_env_value
except Exception:
    def _get_env_value(key, default=None):
        return os.getenv(key, default)

Replaced all os.getenv() calls for env vars in tts_tool.py:

LineBeforeAfter
244os.getenv("ELEVENLABS_API_KEY", "")_get_env_value("ELEVENLABS_API_KEY") or ""
338os.getenv("XAI_API_KEY", "").strip()(_get_env_value("XAI_API_KEY") or "").strip()
349os.getenv("XAI_BASE_URL")_get_env_value("XAI_BASE_URL")
411os.getenv("MINIMAX_API_KEY", "")_get_env_value("MINIMAX_API_KEY") or ""
488os.getenv("MISTRAL_API_KEY", "")_get_env_value("MISTRAL_API_KEY") or ""
583os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")_get_env_value("GEMINI_API_KEY") or _get_env_value("GOOGLE_API_KEY")
594os.getenv("GEMINI_BASE_URL")_get_env_value("GEMINI_BASE_URL")
990os.getenv("ELEVENLABS_API_KEY")_get_env_value("ELEVENLABS_API_KEY")
1000os.getenv("MINIMAX_API_KEY")_get_env_value("MINIMAX_API_KEY")
1002os.getenv("XAI_API_KEY")_get_env_value("XAI_API_KEY")
1004os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")_get_env_value("GEMINI_API_KEY") or _get_env_value("GOOGLE_API_KEY")
1008os.getenv("MISTRAL_API_KEY")_get_env_value("MISTRAL_API_KEY")
1110os.getenv("ELEVENLABS_API_KEY", "")_get_env_value("ELEVENLABS_API_KEY") or ""
1296os.getenv('ELEVENLABS_API_KEY')_get_env_value('ELEVENLABS_API_KEY')
1302os.getenv('MINIMAX_API_KEY')_get_env_value('MINIMAX_API_KEY')

Safety

  • Fallback to os.getenv() if hermes_cli.config is unavailable — no behavior change for existing installations
  • get_env_value() checks os.environ first, then .env file — fully backward-compatible
  • No new dependencies, no API surface changes

Testing

  • Python syntax check passes (py_compile)
  • Pattern matches existing usage in hermes_cli/auth.py (line 332) and hermes_cli/gateway.py (lines 2388+)

Closes #17140

Changed files

  • tools/tts_tool.py (modified, +25/-15)

Code Example

try:
    from hermes_cli.config import get_env_value
except Exception:
    _get_env_value_fallback = os.getenv
    def get_env_value(key, default=None):
        return _get_env_value_fallback(key) or default
RAW_BUFFERClick to expand / collapse

Bug Description

TTS provider tools (elevenlabs, minimax, mistral, xai, gemini) fail with "API key not set" even when the corresponding API key is correctly set in ~/.hermes/.env. The tools use os.getenv("X_API_KEY") directly, which does not read values loaded by the hermes config bridge (hermes_cli.config.get_env_value()).

Steps to Reproduce

  1. Set a TTS API key in ~/.hermes/.env (e.g., MINIMAX_API_KEY=sk-...)
  2. Configure tts.provider: minimax in ~/.hermes/config.yaml
  3. Run any TTS tool call (e.g., /voice on or text_to_speech tool)
  4. Error: TTS configuration error (minimax): MINIMAX_API_KEY not set

Expected Behavior

API keys stored in ~/.hermes/.env should be accessible to all TTS provider tools, the same way they are accessible to the main agent and other tools that use get_env_value().

Actual Behavior

os.getenv("MINIMAX_API_KEY") returns None in the tool context, while get_env_value("MINIMAX_API_KEY") (from hermes_cli.config) correctly returns the key from .env.

Same pattern affects all providers in tools/tts_tool.py:

  • elevenlabs — line 315: os.getenv("ELEVENLABS_API_KEY")
  • xai — line 409: os.getenv("XAI_API_KEY")
  • minimax — line 482: os.getenv("MINIMAX_API_KEY")
  • mistral — line 559: os.getenv("MISTRAL_API_KEY")
  • gemini — line 654: os.getenv("GEMINI_API_KEY")

And their corresponding check_tts_requirements() checks (lines ~1151–1171).

Root Cause Analysis

hermes_cli.config uses a custom .env parser (get_env_value()) that reads ~/.hermes/.env directly. This is distinct from python-dotenv — the .env file is NOT automatically loaded into os.environ by the hermes startup sequence. Tools that call os.getenv() directly find empty values, while tools that use get_env_value() find the actual keys.

This is the same class of bug documented in issue #14693 (tool-layer env vars defeating config routing), just applied to API keys rather than model names.

Proposed Fix

In tools/tts_tool.py, replace all os.getenv("X_API_KEY") calls with get_env_value("X_API_KEY") using the same fallback pattern already used by other hermes tools:

try:
    from hermes_cli.config import get_env_value
except Exception:
    _get_env_value_fallback = os.getenv
    def get_env_value(key, default=None):
        return _get_env_value_fallback(key) or default

Then swap every os.getenv("X_API_KEY", "")get_env_value("X_API_KEY") or "" and every os.getenv("X_API_KEY")get_env_value("X_API_KEY").

Environment

  • OS: Ubuntu (Linux)
  • Hermes Version: 2.x (current main branch)
  • Python: 3.11+

extent analysis

TL;DR

Replace os.getenv("X_API_KEY") calls with get_env_value("X_API_KEY") in tools/tts_tool.py to fix the API key not set issue.

Guidance

  • Identify all occurrences of os.getenv("X_API_KEY") in tools/tts_tool.py and replace them with get_env_value("X_API_KEY").
  • Use the fallback pattern to ensure compatibility: get_env_value("X_API_KEY") or "" for cases where a default value is needed.
  • Verify the fix by running TTS tool calls after applying the changes.
  • Review the hermes_cli.config module to understand how get_env_value() reads environment variables from ~/.hermes/.env.

Example

# Before
api_key = os.getenv("MINIMAX_API_KEY")

# After
from hermes_cli.config import get_env_value
api_key = get_env_value("MINIMAX_API_KEY")

Notes

This fix assumes that hermes_cli.config.get_env_value() correctly reads environment variables from ~/.hermes/.env. If issues persist, review the implementation of get_env_value().

Recommendation

Apply the proposed fix by replacing os.getenv() calls with get_env_value() in tools/tts_tool.py, as it directly addresses the root cause of the issue.

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