ZhuLinsen/daily_stock_analysis · critical · RuntimeError
Hermes API key is a masked placeholder and cannot be used fo
Error message
Hermes API key is a masked placeholder and cannot be used for generation
What it means
RuntimeError raised on the Hermes-only direct path when the deployment's api_key in litellm_params matches the masked-secret placeholder pattern (is_masked_secret_placeholder). Masked placeholders appear when secrets were redacted for display/logging (e.g. '****' or a masked token) and such a value is not a usable credential, so generation refuses rather than sending a guaranteed-401 request.
Source
Thrown at src/analyzer.py:2764
self,
model: str,
call_kwargs: Dict[str, Any],
*,
config: Config,
use_channel_router: bool,
router_model_names: set[str],
) -> Any:
"""Dispatch a LiteLLM completion through router or direct fallback."""
origins = route_deployment_origins(config.llm_model_list, model)
if origins.is_mixed:
raise RuntimeError("Hermes/non-Hermes mixed generation route is not supported without deployment-level no-proxy client support")
if origins.is_hermes_only:
deployment = origins.hermes_deployments[0]
params = dict(deployment.get("litellm_params") or {})
api_key = str(params.get("api_key") or "").strip()
base_url = str(params.get("api_base") or "").strip()
if is_masked_secret_placeholder(api_key):
raise RuntimeError("Hermes API key is a masked placeholder and cannot be used for generation")
timeout = float(call_kwargs.get("timeout") or 30.0)
hermes_kwargs = dict(call_kwargs)
hermes_kwargs["model"] = str(params.get("model") or model)
hermes_kwargs["stream"] = False
hermes_kwargs.pop("api_key", None)
hermes_kwargs.pop("api_base", None)
with open_hermes_no_proxy_client(api_key=api_key, base_url=base_url, timeout=timeout) as client:
hermes_kwargs["client"] = client
return litellm.completion(**hermes_kwargs)
wire_models = resolve_fallback_litellm_wire_models(model, config.llm_model_list)
register_fallback_model_pricing(wire_models)
effective_kwargs = dict(call_kwargs)
if use_channel_router and self._router and model in router_model_names:
return self._router.completion(**effective_kwargs)
if self._router and model == config.litellm_model and not use_channel_router:
return self._router.completion(**effective_kwargs)
View on GitHub (pinned to 5159bd72e8)
Solutions
- Re-enter the real Hermes API key into the deployment's litellm_params (via env var or secret store, not by pasting into chat).
- Check how the config was produced: if it round-tripped through any redaction/sanitization layer, source it from the original secret store instead.
- Add a startup assertion that no api_key in llm_model_list satisfies is_masked_secret_placeholder, so the failure surfaces at boot with the deployment name rather than at generation time.
Example fix
# before
litellm_params: {model: ..., api_base: ..., api_key: "********"} # masked dump re-imported
# after
litellm_params: {model: ..., api_base: ..., api_key: "${HERMES_API_KEY}"} # resolved from env/secret store at load Defensive patterns
Strategy: validation
Validate before calling
from src.utils.secrets import is_masked_secret_placeholder # actual import path per repo
for model in config.llm_model_list:
for dep in model.get("litellm_params", {}).get("api_key", ""):
if is_masked_secret_placeholder(str(dep)):
raise ConfigError("masked API key detected; inject real secrets") Try / catch
try:
result = analyzer._dispatch_litellm_completion(...)
except RuntimeError as e:
if "masked placeholder" in str(e):
# credential injection failed upstream; fix secret source, never retry as-is
raise ConfigError("Hermes api_key is masked; re-inject real secret") from e
raise Prevention
- Never re-import config dumps produced by redaction/logging layers.
- Assert at startup that no api_key is a masked placeholder.
- Manage keys via the secret store (devkey/env), never pasted values.
When it happens
Trigger: LLM generation routed to a Hermes deployment whose litellm_params.api_key is a masked/redacted placeholder string — typically because a sanitized config dump (logs, UI payload, exported settings) was re-imported as the real config, or env substitution never ran and the literal mask text survived.
Common situations: Copying an LLM_MODEL_LIST from a web UI or log output where keys were masked; CI that serializes config with redaction and then reloads it; a secrets-injection step (devkey/env) skipped or failing silently so the placeholder remains.
Related errors
- Hermes/non-Hermes mixed generation route is not supported wi
- Hermes only supports PROTOCOL=openai
- Hermes BASE_URL must use http or https
- Hermes BASE_URL must include a loopback host
- Hermes BASE_URL must not include userinfo
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/439ccf6b60671293.
Report an issue: GitHub.