# NousResearch/hermes-agent · Lifting MistralAI ban after clean 2.4.8 release

## Open now

- Nothing open on GitHub.

These items address restoring or re-enabling MistralAI for STT/TTS after a previous blanket ban, contingent on version 2.4.8 being considered clean. Some approaches use an opt-in gate.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #34841. Verify it addresses your case.
- **Recommended:** read PR #34841 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-34841`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #34503: fix(stt,tts): lift mistralai blanket-ban — 2.4.8+ is clean

## Prior & competing PRs

- **#34841: fix(stt,tts): restore mistralai — 2.4.8 is clean, ban lifted**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/security_advisories.py`, `hermes_cli/tools_config.py`, `hermes_cli/web_server.py`, `pyproject.toml`, `tests/test_project_metadata.py`, `tests/tools/test_transcription_dotenv_fallback.py`, `tests/tools/test_transcription_tools.py`, `tests/tools/test_tts_mistral.py`, `tools/lazy_deps.py`, `tools/transcription_tools.py`, `tools/tts_tool.py`, `website/docs/user-guide/features/code-execution.md`
  - fix hunks:
  - `hermes_cli/security_advisories.py`

    ```diff
    @@ -104,7 +104,9 @@ class Advisory:
                 "them to a hardcoded webhook. If you ran any Python process that "
                 "imported mistralai 2.4.6 — including hermes when configured "
                 "with provider=mistral for TTS or STT — assume those credentials "
    -            "are exposed."
    +            "are exposed. PyPI has since removed 2.4.6 and the project ships "
    +            "clean releases again (2.4.7, 2.4.8); this advisory only fires if "
    +            "the compromised 2.4.6 is still installed."
             ),
             url="https://socket.dev/blog/mini-shai-hulud-worm-pypi",
             compromised=(
    ```
  - `hermes_cli/tools_config.py`

    ```diff
    @@ -244,9 +244,16 @@ def _get_plugin_toolset_keys() -> set:
                     ],
                     "tts_provider": "elevenlabs",
                 },
    -            # Mistral (Voxtral TTS) temporarily hidden — `mistralai` PyPI
    -            # package is currently quarantined (malicious 2.4.6 release on
    -            # 2026-05-12). Restore this entry once PyPI un-quarantines.
    +            # Mistral Voxtral TTS — `mistralai` SDK lazy-installs on first use.
    +            {
    +                "name": "Mistral (Voxtral TTS)",
    +                "badge": "paid",
    +                "tag": "Multilingual, native Opus",
    +                "env_vars": [
    +                    {"key": "MISTRAL_API_KEY", "prompt": "Mistral API key", "url": "https://console.mistral.ai/"},
    +                ],
    +                "tts_provider": "mistral",
    +            },
                 {
                     "name": "Google Gemini TTS",
                     "badge": "preview",
    ```
  - `hermes_cli/web_server.py`

    ```diff
    @@ -320,9 +320,7 @@ async def auth_middleware(request: Request, call_next):
         "stt.provider": {
             "type": "select",
             "description": "Speech-to-text provider",
    -        # "mistral" temporarily removed — mistralai PyPI package quarantined
    -        # (malicious 2.4.6 release on 2026-05-12). Restore once available.
    -        "options": ["local", "openai"],
    +        "options": ["local", "openai", "mistral"],
         },
         "display.skin": {
             "type": "select",
    ```
  - `pyproject.toml`

    ```diff
    @@ -117,22 +117,15 @@ sms = ["aiohttp==3.13.3"]
     # to it, which is already provided by the `mcp` extra.
     computer-use = ["mcp==1.26.0"]
     acp = ["agent-client-protocol==0.9.0"]
    -# mistral: extra REMOVED 2026-05-12 — `mistralai` PyPI project quarantined
    -# after malicious 2.4.6 release (Mini Shai-Hulud worm). Every version of
    -# `mistralai` returns 404 on PyPI right now, so any pin we'd write is
    -# unresolvable, which breaks `uv lock --check` in CI.
    -#
    -# To restore once PyPI un-quarantines:
    -#   1. Verify the new release is clean (read the changelog, check Socket
    -#      advisory page, confirm no malicious code review findings).
    -#   2. Add back: mistral = ["mistralai==<verified-version>"]
    -#   3. Re-enable Mistral in:
    -#        - tools/lazy_deps.py (LAZY_DEPS["tts.mistral"], LAZY_DEPS["stt.mistral"])
    -#        - hermes_cli/tools_config.py (un-hide from provider picker)
    -#        - hermes_cli/web_server.py (re-add to dashboard STT options)
    -#        - tools/transcription_tools.py / tools/tts_tool.py (drop disabled stubs)
    -#   4. Run `uv lock` to regenerate transitives.
    -#   5. Optionally re-add to [all] only after a few days of clean operation.
    +# mistral: Voxtral STT + TTS. Pinned to an exact verified-clean version.
    +# The `mistralai` PyPI project was quarantined 2026-05-12 after the malicious
    +# 2.4.6 release (Mini Shai-Hulud worm); 2.4.6 was removed from PyPI and the
    +# project is serving clean releases again (2.4.7 2026-05-25, 2.4.8 2026-05-28).
    +# Like other opt-in TTS/STT backends, this is lazy-installed via
    +# tools/lazy_deps.py (stt.mistral / tts.mistral) at first use — deliberately
    +# NOT re-added to [all] so a future quarantined release can't break fresh
    +# installs (see [all] policy comment below).
    +mistral = ["mistralai==2.4.8"]
     bedrock = ["boto3==1.42.89"]
     azure-identity = ["azure-identity==1.25.3"]
     termux = [
    ```
  - `tests/test_project_metadata.py`

    ```diff
    @@ -73,6 +73,7 @@ def test_lazy_installable_extras_excluded_from_all():
             "modal", "daytona",
             "messaging", "slack", "matrix", "dingtalk", "feishu",
             "honcho", "hindsight",
    +        "mistral",  # mistralai — Voxtral STT/TTS, lazy-installed (stt.mistral / tts.mistral)
         }
         all_extra_specs = optional_dependencies["all"]
         for extra in lazy_covered_extras:
    ```
  - `tests/tools/test_transcription_dotenv_fallback.py`

    ```diff
    @@ -99,12 +99,6 @@ def test_explicit_groq_sees_dotenv(self):
                 assert tt._get_provider({"enabled": True, "provider": "groq"}) == "groq"
     
         def test_explicit_mistral_sees_dotenv(self):
    -        """Mistral STT is intentionally disabled (PyPI quarantine 2026-05-12).
    -
    -        Even with the dotenv key visible, explicit `provider: mistral` must
    -        return "none" with a warning. Restore the previous behavior once
    -        `mistralai` is un-quarantined on PyPI.
    -        """
             from tools import transcription_tools as tt
     
             with patch.object(tt, "_HAS_FASTER_WHISPER", False), \
    ```

    ```diff
    @@ -112,7 +106,7 @@ def test_explicit_mistral_sees_dotenv(self):
                  patch.object(tt, "_has_local_command", return_value=False), \
                  patch("hermes_cli.config.load_env",
                        return_value={"MISTRAL_API_KEY": "[redacted:secret-assignment]"}):
    -            assert tt._get_provider({"enabled": True, "provider": "mistral"}) == "none"
    +            assert tt._get_provider({"enabled": True, "provider": "mistral"}) == "mistral"
     
         def test_explicit_xai_sees_dotenv(self):
             from tools import transcription_tools as tt
    ```
  - `tests/tools/test_transcription_tools.py`

    ```diff
    @@ -1010,23 +1010,16 @@ def test_permission_error(self, monkeypatch, sample_ogg, mock_mistral_module):
     # ============================================================================
     
     class TestGetProviderMistral:
    -    """Mistral-specific provider selection tests.
    -
    -    Mistral STT is intentionally disabled in 2026-05-12+ while the
    -    `mistralai` PyPI package is quarantined. These tests document that
    -    explicit `provider: mistral` always returns "none" with a warning, and
    -    that auto-detect skips mistral entirely.
    -    """
    +    """Mistral-specific provider selection tests."""
     
         def test_mistral_when_key_and_sdk_available(self, monkeypatch):
    -        """Even with key + SDK, explicit mistral returns 'none' (disabled)."""
             monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
             with patch("tools.transcription_tools._HAS_MISTRAL", True):
                 from tools.transcription_tools import _get_provider
    -            assert _get_provider({"provider": "mistral"}) == "none"
    +            assert _get_provider({"provider": "mistral"}) == "mistral"
     
         def test_mistral_explicit_no_key_returns_none(self, monkeypatch):
    -        """Explicit mistral with no key returns none."""
    +        """Explicit mistral with no key returns none — no cross-provider fallback."""
             monkeypatch.delenv("MISTRAL_API_KEY", raising=False)
             with patch("tools.transcription_tools._HAS_MISTRAL", True):
                 from tools.transcription_tools import _get_provider
    ```

    ```diff
    @@ -1039,23 +1032,18 @@ def test_mistral_explicit_no_sdk_returns_none(self, monkeypatch):
                 from tools.transcription_tools import _get_provider
                 assert _get_provider({"provider": "mistral"}) == "none"
     
    -    def test_auto_detect_skips_mistral(self, monkeypatch):
    -        """Auto-detect intentionally skips mistral (quarantine workaround).
    -
    -        With no other provider available but MISTRAL_API_KEY set, the result
    -        must be 'none' — mistral is no longer in the auto-detect chain.
    -        """
    +    def test_auto_detect_mistral_after_openai(self, monkeypatch):
    +        """Auto-detect: mistral is tried after openai when both are unavailable."""
             monkeypatch.delenv("GROQ_API_KEY", raising=False)
             monkeypatch.delenv("VOICE_TOOLS_OPENAI_KEY", raising=False)
             monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    -        monkeypatch.delenv("XAI_API_KEY", raising=False)
             monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
             with patch("tools.transcription_tools._HAS_FASTER_WHISPER", False), \
                  patch("tools.transcription_tools._has_local_command", return_value=False), \
                  patch("tools.transcription_tools._HAS_OPENAI", False), \
                  patch("tools.transcription_tools._HAS_MISTRAL", True):
                 from tools.transcription_tools import _get_provider
    -            assert _get_provider({}) == "none"
    +            assert _get_provider({}) == "mistral"
     
         def test_auto_detect_openai_preferred_over_mistral(self, monkeypatch):
             """Auto-detect: openai is preferred over mistral (both paid, openai more common)."""
    ```

    ```diff
    @@ -1329,13 +1317,8 @@ def test_auto_detect_xai_after_mistral(self, monkeypatch):
                 from tools.transcription_tools import _get_provider
                 assert _get_provider({}) == "xai"
     
    -    def test_auto_detect_mistral_skipped_xai_wins(self, monkeypatch):
    -        """Auto-detect skips mistral entirely (quarantine) — xai wins.
    -
    -        Even with MISTRAL_API_KEY set, mistral is no longer in the
    -        auto-detect chain. xai is the next-best fallback when the
    -        local/groq/openai chain is unavailable.
    -        """
    +    def test_auto_detect_mistral_preferred_over_xai(self, monkeypatch):
    +        """Auto-detect: mistral is preferred over xai."""
             monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
             monkeypatch.setenv("XAI_API_KEY", "xai-test")
             monkeypatch.delenv("GROQ_API_KEY", raising=False)
    ```

    ```diff
    @@ -1346,7 +1329,7 @@ def test_auto_detect_mistral_skipped_xai_wins(self, monkeypatch):
                  patch("tools.transcription_tools._HAS_OPENAI", False), \
                  patch("tools.transcription_tools._HAS_MISTRAL", True):
                 from tools.transcription_tools import _get_provider
    -            assert _get_provider({}) == "xai"
    +            assert _get_provider({}) == "mistral"
     
         def test_auto_detect_no_key_returns_none(self, monkeypatch):
             """Auto-detect: xai skipped when no key is set."""
    ```
  - `tests/tools/test_tts_mistral.py`

    ```diff
    @@ -162,34 +162,27 @@ def test_model_from_config_overrides_default(
     
     
     class TestTtsDispatcherMistral:
    -    def test_dispatcher_returns_disabled_error(
    +    def test_dispatcher_routes_to_mistral(
             self, tmp_path, mock_mistral_module, monkeypatch
         ):
    -        """Mistral TTS is intentionally disabled (PyPI quarantine 2026-05-12).
    -
    -        The dispatcher must short-circuit with a clear status message before
    -        attempting any SDK import, even when MISTRAL_API_KEY is set and a
    -        mock SDK is wired in. Restore routing once `mistralai` is
    -        un-quarantined on PyPI.
    -        """
             import json
     
             from tools.tts_tool import text_to_speech_tool
     
             monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
    +        mock_mistral_module.audio.speech.complete.return_value = MagicMock(
    +            audio_data=base64.b64encode(b"audio").decode()
    +        )
     
             output_path = str(tmp_path / "out.mp3")
             with patch("tools.tts_tool._load_tts_config", return_value={"provider": "mistral"}):
                 result = json.loads(text_to_speech_tool("Hello", output_path=output_path))
     
    -        assert result["success"] is False
    -        assert "temporarily disabled" in result["error"]
    -        assert "quarantined" in result["error"]
    -        # SDK must not have been called.
    -        mock_mistral_module.audio.speech.complete.assert_not_called()
    +        assert result["success"] is True
    +        assert result["provider"] == "mistral"
    +        mock_mistral_module.audio.speech.complete.assert_called_once()
     
         def test_dispatcher_returns_error_when_sdk_not_installed(self, tmp_path, monkeypatch):
    -        """Same disabled message regardless of SDK presence."""
             import json
     
             from tools.tts_tool import text_to_speech_tool
    ```

    ```diff
    @@ -203,7 +196,7 @@ def test_dispatcher_returns_error_when_sdk_not_installed(self, tmp_path, monkeyp
                 )
     
             assert result["success"] is False
    -        assert "temporarily disabled" in result["error"]
    +        assert "mistralai" in result["error"]
     
     
     class TestCheckTtsRequirementsMistral:
    ```
  - `tools/lazy_deps.py`

    ```diff
    @@ -97,15 +97,16 @@
         # (see comment at top of [project.dependencies]). When bumping, update
         # both this map AND the corresponding extra in pyproject.toml.
         #
    -    # NOTE: tts.mistral / stt.mistral entries are intentionally absent —
    -    # the `mistralai` PyPI project is quarantined as of 2026-05-12 (Mini
    -    # Shai-Hulud worm). Re-add when PyPI restores a clean release; see
    -    # comment in pyproject.toml above the (removed) `mistral` extra for
    -    # the full restoration checklist.
    +    # mistralai pin tracks the `mistral` extra in pyproject.toml. PyPI
    +    # quarantined the project 2026-05-12 (malicious 2.4.6, Mini Shai-Hulud);
    +    # 2.4.6 was removed and clean releases resumed (2.4.7, 2.4.8). Voxtral
    +    # STT + TTS share the same SDK.
    +    "tts.mistral": ("mistralai==2.4.8",),
         "tts.edge": ("edge-tts==7.2.7",),
         "tts.elevenlabs": ("elevenlabs==1.59.0",),
     
         # ─── Speech-to-text providers ──────────────────────────────────────────
    +    "stt.mistral": ("mistralai==2.4.8",),
         "stt.faster_whisper": (
             "faster-whisper==1.2.1",
             "sounddevice==0.5.5",
    ```
  - `tools/transcription_tools.py`

    ```diff
    @@ -792,16 +792,11 @@ def _get_provider(stt_config: dict) -> str:
                 return "none"
     
             if provider == "mistral":
    -            # `mistralai` PyPI package was quarantined on 2026-05-12 after a
    -            # malicious 2.4.6 release. Refuse to use this provider until it's
    -            # available again so we surface a clear message instead of an
    -            # opaque ImportError mid-call.
    +            if _HAS_MISTRAL and get_env_value("MISTRAL_API_KEY"):
    +                return "mistral"
                 logger.warning(
    -                "STT provider 'mistral' (Voxtral Transcribe) is temporarily "
    -                "disabled — `mistralai` PyPI package is quarantined "
    -                "(malicious 2.4.6 release on 2026-05-12). Falling back to "
    -                "another provider. Set stt.provider in config.yaml to 'local' "
    -                "or 'openai' to silence this warning."
    +                "STT provider 'mistral' configured but mistralai package "
    +                "not installed or MISTRAL_API_KEY not set"
                 )
                 return "none"
     
    ```

    ```diff
    @@ -817,9 +812,7 @@ def _get_provider(stt_config: dict) -> str:
     
             return provider  # Unknown — let it fail downstream
     
    -    # --- Auto-detect (no explicit provider): local > groq > openai > xai ---
    -    # mistral is intentionally skipped while `mistralai` is quarantined on
    -    # PyPI (malicious 2.4.6 release on 2026-05-12).
    +    # --- Auto-detect (no explicit provider): local > groq > openai > mistral > xai ---
     
         if _HAS_FASTER_WHISPER:
             return "local"
    ```

    ```diff
    @@ -834,6 +827,12 @@ def _get_provider(stt_config: dict) -> str:
         if _HAS_OPENAI and _has_openai_audio_backend():
             logger.info("No local STT available, using OpenAI Whisper API")
             return "openai"
    +    # Only auto-select Mistral if the SDK is already present — don't trigger a
    +    # lazy-install during passive auto-detection. Explicit `provider: mistral`
    +    # (above) does lazy-install on first transcription call.
    +    if _HAS_MISTRAL and get_env_value("MISTRAL_API_KEY"):
    +        logger.info("No local STT available, using Mistral Voxtral Transcribe API")
    +        return "mistral"
         try:
             from tools.xai_http import resolve_xai_http_credentials
     
    ```

    ```diff
    @@ -1371,6 +1370,11 @@ def _transcribe_mistral(file_path: str, model_name: str) -> Dict[str, Any]:
             return {"success": False, "transcript": "", "error": "MISTRAL_API_KEY not set"}
     
         try:
    +        try:
    +            from tools.lazy_deps import ensure as _lazy_ensure
    +            _lazy_ensure("stt.mistral", prompt=False)
    +        except ImportError:
    +            pass
             from mistralai.client import Mistral
     
             with Mistral(api_key=[redacted:secret-assignment] as client:
    ```
  - `tools/tts_tool.py`

    ```diff
    @@ -121,7 +121,20 @@ def _import_openai_client():
         return OpenAIClient
     
     def _import_mistral_client():
    -    """Lazy import Mistral client. Returns the class or raises ImportError."""
    +    """Lazy import Mistral client. Returns the class or raises ImportError.
    +
    +    Calls :func:`tools.lazy_deps.ensure` first so the ``mistralai`` SDK gets
    +    installed on demand if the user picked Mistral as their STT/TTS provider
    +    but never ran the post-setup hook (e.g. enabled it by editing config.yaml
    +    directly). Mirrors the ElevenLabs lazy-import path.
    +    """
    +    try:
    +        from tools.lazy_deps import ensure
    +        ensure("tts.mistral", prompt=False)
    +    except ImportError:
    +        pass
    +    except Exception as e:  # FeatureUnavailable or any unexpected error
    +        raise ImportError(str(e))
         from mistralai.client import Mistral
         return Mistral
     
    ```

    ```diff
    @@ -1974,21 +1987,16 @@ def text_to_speech_tool(
                 _generate_xai_tts(text, file_str, tts_config)
     
             elif provider == "mistral":
    -            # `mistralai` PyPI package was quarantined on 2026-05-12 after a
    -            # malicious 2.4.6 release. Surface a clear status message instead
    -            # of attempting an import that would either fail or pull a stale
    -            # cached package.
    -            return json.dumps({
    -                "success": False,
    -                "error": (
    -                    "Mistral Voxtral TTS is temporarily disabled. The "
    -                    "`mistralai` PyPI package was quarantined on 2026-05-12 "
    -                    "after a malicious 2.4.6 release. Switch tts.provider in "
    -                    "config.yaml to 'edge', 'elevenlabs', 'openai', 'minimax', "
    -                    "'gemini', 'xai', 'neutts', or 'kittentts'. Mistral "
    -                    "support will return once PyPI un-quarantines the package."
    -                ),
    -            }, ensure_ascii=False)
    +            try:
    +                _import_mistral_client()
    +            except ImportError:
    +                return json.dumps({
    +                    "success": False,
    +                    "error": "Mistral provider selected but 'mistralai' package not installed. "
    +                             "Run: pip install 'hermes-agent[mistral]'"
    +                }, ensure_ascii=False)
    +            logger.info("Generating speech with Mistral Voxtral TTS...")
    +            _generate_mistral_tts(text, file_str, tts_config)
     
             elif provider == "gemini":
                 logger.info("Generating speech with Google Gemini TTS...")
    ```
  - `uv.lock`

    ```diff
    @@ -1243,6 +1243,15 @@ wheels = [
         { url = "https://files.pythonhosted.org/packages/97/a8/c070e1340636acb38d4e6a7e45c46d168a462b48b9b3257e14ca0e5af79b/environs-14.6.0-py3-none-any.whl", hash = "sha256:f8fb3d6c6a55872b0c6db077a28f5a8c7b8984b7c32029613d44cef95cfc0812", size = 17205, upload-time = "2026-02-20T04:02:07.299Z" },
     ]
     
    +[[package]]
    +name = "eval-type-backport"
    +version = "0.3.1"
    +source = { registry = "https://pypi.org/simple" }
    +sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" }
    +wheels = [
    +    { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" },
    +]
    +
     [[package]]
     name = "exa-py"
     version = "2.10.2"
    ```

    ```diff
    @@ -1715,6 +1724,9 @@ messaging = [
         { name = "slack-bolt" },
         { name = "slack-sdk" },
     ]
    +mistral = [
    +    { name = "mistralai" },
    +]
     modal = [
         { name = "modal" },
     ]
    ```

    ```diff
    @@ -1840,6 +1852,7 @@ requires-dist = [
         { name = "mcp", marker = "extra == 'computer-use'", specifier = "==1.26.0" },
         { name = "mcp", marker = "extra == 'dev'", specifier = "==1.26.0" },
         { name = "mcp", marker = "extra == 'mcp'", specifier = "==1.26.0" },
    +    { name = "mistralai", marker = "extra == 'mistral'", specifier = "==2.4.8" },
         { name = "modal", marker = "extra == 'modal'", specifier = "==1.3.4" },
         { name = "numpy", marker = "extra == 'voice'", specifier = "==2.4.3" },
         { name = "openai", specifier = "==2.24.0" },
    ```

    ```diff
    @@ -1876,7 +1889,7 @@ requires-dist = [
         { name = "uvicorn", extras = ["standard"], marker = "extra == 'web'", specifier = "==0.41.0" },
         { name = "youtube-transcript-api", marker = "extra == 'youtube'", specifier = "==1.2.4" },
     ]
    -provides-extras = ["anthropic", "exa", "firecrawl", "parallel-web", "fal", "edge-tts", "modal", "daytona", "hindsight", "dev", "messaging", "cron", "slack", "matrix", "wecom", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "computer-use", "acp", "bedrock", "azure-identity", "termux", "termux-all", "dingtalk", "feishu", "google", "youtube", "web", "all"]
    +provides-extras = ["anthropic", "exa", "firecrawl", "parallel-web", "fal", "edge-tts", "modal", "daytona", "hindsight", "dev", "messaging", "cron", "slack", "matrix", "wecom", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "computer-use", "acp", "mistral", "bedrock", "azure-identity", "termux", "termux-all", "dingtalk", "feishu", "google", "youtube", "web", "all"]
     
     [[package]]
     name = "hf-xet"
    ```

    ```diff
    @@ -2206,6 +2219,15 @@ wheels = [
         { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" },
     ]
     
    +[[package]]
    +name = "jsonpath-python"
    +version = "1.1.6"
    +source = { registry = "https://pypi.org/simple" }
    +sdist = { url = "https://files.pythonhosted.org/packages/98/18/4ca8742534a5993ff383f7602e325ce2d5d7cc93d72ac5e1cdedbea8a458/jsonpath_python-1.1.6.tar.gz", hash = "sha256:dded9932b4ec41fb8726e09c83afa4e6be618f938c2db287cc2a81723c639671", size = 88178, upload-time = "2026-05-07T01:26:34.482Z" }
    +wheels = [
    +    { url = "https://files.pythonhosted.org/packages/55/8a/1270a6803bd821cbfcdda387eaa13cb41a7b1f7b9bd145979b3bfb9d6cb7/jsonpath_python-1.1.6-py3-none-any.whl", hash = "sha256:a1c50afd8d3fbbaf47a4873bc890dcb3c15da96f5c020327977d844d8731a2d4", size = 14453, upload-time = "2026-05-07T01:26:33.306Z" },
    +]
    +
     [[package]]
     name = "jsonschema"
     version = "4.26.0"
    ```

    ```diff
    @@ -2408,6 +2430,25 @@ wheels = [
         { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
     ]
     
    +[[package]]
    +name = "mistralai"
    +version = "2.4.8"
    +source = { registry = "https://pypi.org/simple" }
    +dependencies = [
    +    { name = "eval-type-backport" },
    +    { name = "httpx" },
    +    { name = "jsonpath-python" },
    +    { name = "opentelemetry-api" },
    +    { name = "opentelemetry-semantic-conventions" },
    +    { name = "pydantic" },
    +    { name = "python-dateutil" },
    +    { name = "typing-inspection" },
    +]
    +sdist = { url = "https://files.pythonhosted.org/packages/55/1c/04119828a3da3be8c79efbe59035a621ae22af873c1ee5a4200355025aa6/mistralai-2.4.8.tar.gz", hash = "sha256:4f27b9b7dfd564ae111d3d9992d2a8ad1454aaf3e7675554c686aa3bb89617e2", size = 464443, upload-time = "2026-05-28T10:00:45.72Z" }
    +wheels = [
    +    { url = "https://files.pythonhosted.org/packages/74/2a/d9952a97596ff9570ff7f486084ebfc5637b1bcf62084b97c0f8415713fc/mistralai-2.4.8-py3-none-any.whl", hash = "sha256:edc445c8b5edf332d45db6c708cd1e4d3f62e6eba5d2e8bf3969bdc5117f6472", size = 1110598, upload-time = "2026-05-28T10:00:43.939Z" },
    +]
    +
     [[package]]
     name = "modal"
     version = "1.3.4"
    ```
  - `website/docs/user-guide/features/code-execution.md`

    ```diff
    @@ -219,6 +219,62 @@ terminal:
     
     See the [Security guide](/user-guide/security#environment-variable-passthrough) for full details.
     
    +### `HERMES_*` variables in the child
    +
    +The child process receives only a small, fixed set of operational `HERMES_*`
    +variables by exact name:
    +
    +- `HERMES_HOME`
    +- `HERMES_PROFILE`
    +- `HERMES_CONFIG`
    +- `HERMES_ENV`
    +
    +(plus `HERMES_RPC_DIR` / `HERMES_RPC_SOCKET` / `TZ` / `HOME`, which Hermes
    +injects explicitly so the RPC channel works).
    +
    +:::note Behavior change
    +Earlier versions passed **any** variable whose name began with `HERMES_`
    +through to the child. That broad prefix was removed for security hardening: it
    +could leak `HERMES_*`-named configuration that doesn't match a secret substring
    +(for example `HERMES_BASE_URL`, `HERMES_KANBAN_DB`, or a `HERMES_*_WEBHOOK`
    +endpoint) into arbitrary sandboxed code.
    +
    +If an `execute_code` script — or a repo/plugin module it imports at import time
    +— relied on a `HERMES_*` variable outside the four operational names above, it
    +will now find that variable **unset** in the child. The drop is intentional,
    +not a bug.
    +:::
    +
    +**Workaround — opt the variable back in explicitly.** Both routes pass the
    +variable through `execute_code` *and* `terminal` children, and neither weakens
    +the secret-stripping guarantee (Hermes-managed provider credentials can never
    +be re-allowed this way):
    +
    +1. **Per-machine, in `config.yaml`** — add the exact variable name to the
    +   passthrough allowlist:
    +
    +   ```yaml
    +   terminal:
    +     env_passthrough:
    +       - HERMES_KANBAN_DB
    +       - HERMES_BASE_URL
    +   ```
    +
    +2. **Per-skill, in the skill's frontmatter** — declare it so it is registered
    +   automatically whenever that skill is loaded:
    +
    +   ```yaml
    +   required_environment_variables:
    +     - HERMES_KANBAN_DB
    +   ```
    +
    +**Diagnosing it.** When the child drops one or more non-allowlisted `HERMES_*`
    +variables, Hermes emits a one-line `debug` log naming them and pointing at the
    +`env_passthrough` escape hatch. Run with debug logging (`hermes logs --level
    +DEBUG`, or check `~/.hermes/logs/agent.log`) and look for
    +`execute_code: dropped N non-allowlisted HERMES_* var(s)` if a script behaves
    +as though a `HERMES_*` variable is missing.
    +
     Hermes always writes the script and the auto-generated `hermes_tools.py` RPC stub into a temp staging directory that is cleaned up after execution. In `strict` mode the script also *runs* there; in `project` mode it runs in the session's working directory (the staging directory stays on `PYTHONPATH` so imports still resolve). The child process runs in its own process group so it can be cleanly killed on timeout or interruption.
     
     ## execute_code vs terminal
    ```
- **#34506: fix(tts): lift mistralai blanket-ban behind opt-in gate (#34503)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `3a2c03061`, `#34841`

## Fix status

- Merged to main in PR #34841 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-29`.
- Subsystem: `skills`
