hermes - ✅(Solved) Fix [Bug]: On start, Hermes Agent is santizing GLM_API_KEY/GLM_BASE_URL into G\nLM_API_KEY and G\nLM_BASE_URL [5 pull requests, 2 comments, 3 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#17138Fetched 2026-04-29 06:37:10
View on GitHub
Comments
2
Participants
3
Timeline
17
Reactions
2
Timeline (top)
cross-referenced ×5labeled ×5commented ×2referenced ×2

Error Message

Additional Logs / Traceback (optional)

Root Cause

Root Cause Analysis (optional)

Fix Action

Fixed

PR fix notes

PR #17141: fix(cli): prevent .env sanitizer from splitting GLM_API_KEY by LM_API_KEY suffix

Description (problem / solution / changelog)

What does this PR do?

Fix a corruption bug in _sanitize_env_lines where any registered env var name that is a suffix of another (e.g. LM_API_KEY is a suffix of GLM_API_KEY) would cause the longer key's line to be silently split into corrupted halves like G\nLM_API_KEY=.... This rewrote the user's .env file on disk and disabled provider auth for the affected key (Z.AI/GLM in the report; same shape applies to GLM_BASE_URL vs LM_BASE_URL).

The reporter pinpointed the exact mechanism: the splitter walks each .env line scanning for any known-key name as a substring, with no word-boundary check. stripped.find(\"LM_API_KEY=\") returns 1 inside \"GLM_API_KEY=...\" because LM_API_KEY= is a literal suffix of GLM_API_KEY=.

Related Issue

Fixes #17138

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Changes Made

  • hermes_cli/config.py_sanitize_env_lines: collect full needle ranges (start, end) for every known-key match, then drop matches whose range is fully contained within a longer overlapping match. This kills suffix-collision splits while keeping every legitimate concatenation case (where matches do not nest).
  • tests/hermes_cli/test_config.py — two new tests under TestSanitizeEnvLines:
    • test_glm_suffix_collision_not_split — direct regression for #17138.
    • test_suffix_collision_does_not_break_real_concatenation — confirms a genuine concat GLM_API_KEY=glmLM_API_KEY=lm-key still splits at the second key, since that match is not nested inside the first.

How to Test

pytest tests/hermes_cli/test_config.py::TestSanitizeEnvLines -v

All 13 tests (11 existing + 2 new) pass. Full `tests/hermes_cli/test_config.py` suite (52 tests) also passes.

Manual repro:

  1. Put `GLM_API_KEY=glm-secret` in `~/.hermes/.env`.
  2. Run `hermes` (or any code path that triggers `sanitize_env_file`).
  3. Before the fix: the file is rewritten as `G\nLM_API_KEY=glm-secret` and `os.getenv("GLM_API_KEY")` returns `None`. After the fix: the file is unchanged.

Checklist

  • Tests added and pass locally
  • Conventional Commit format
  • Single logical change, scope limited to the sanitizer
  • No hardcoded `~/.hermes` paths (uses existing `HERMES_HOME` flow)
  • No `simple_term_menu` / ANSI erase / cross-toolset schema references
  • No `prompt_toolkit` cache-breaking changes

AI Disclosure

This bug was identified and fixed with AI assistance.

Changed files

  • hermes_cli/config.py (modified, +14/-5)
  • tests/hermes_cli/test_config.py (modified, +17/-0)

PR #17241: fix(config): sanitize_env substring false positive breaks valid API keys

Description (problem / solution / changelog)

Problem

_sanitize_env_lines() in hermes_cli/config.py uses str.find() to detect concatenated KEY=VALUE pairs on a single line. This substring search causes false positives when one known env var name is a substring of another:

  • LM_API_KEY= matches at position 1 inside GLM_API_KEY=<value>, because find() does not require a word boundary.
  • The sanitizer then splits the valid entry into a bare G line and an LM_API_KEY=... line, destroying the key.
  • On the next gateway startup, sanitize_env_file() runs again and sees the two fragments as-is (neither is a valid known key), preserving the corruption permanently.

This is a general bug — any pair of known keys where one name contains another as a substring is affected (e.g., API_KEY= inside OPENAI_API_KEY=, ANTHROPIC_TOKEN= inside ANTHROPIC_API_KEY=, etc.). The practical impact is limited because most users do not encounter the initial corruption that triggers the cycle.

Trigger scenario (CJK input methods)

The bug becomes a persistent data-loss issue when combined with CJK input methods (fcitx5, ibus). These can occasionally insert a stray newline in the middle of a pasted env var name, producing:

G
LM_API_KEY=<actual_value>

Once this corruption exists:

  1. The old sanitizer has no merge logic — it preserves both fragments as-is.
  2. If the user manually fixes the split (e.g., via sed), the sanitizer re-splits it on the next startup due to the substring match described above.
  3. This creates a repair loop that makes the .env file unfixable without patching the code.

Fix

Pass 0 (new): Detect and merge split key names. If a line contains only uppercase letters/underscores (no =) and the next non-empty line, when concatenated, forms a known env var name with =, merge them.

Pass 1 (improved): Replace str.find() (arbitrary substring search) with str.startswith() at position 0 + forward offset scanning. This ensures concatenated key detection only matches actual key names at line/segment boundaries, never as substrings inside other key names.

Testing

Verified with these scenarios:

  • Split key merge: G + newline + LM_API_KEY=<value>GLM_API_KEY=<value>
  • Normal key preserved: GLM_API_KEY=abcGLM_API_KEY=abc
  • Concatenated keys still split: KEY1=val1KEY2=val2 → two lines
  • Comments/blanks preserved
  • No false merge on unrelated uppercase text

Changed files

  • hermes_cli/config.py (modified, +82/-19)

PR #17273: fix(config): prevent .env sanitizer from corrupting GLM_API_KEY and other suffix-trap keys

Description (problem / solution / changelog)

Summary

The _sanitize_env_lines() function in hermes_cli/config.py silently corrupts API keys on every startup. It uses str.find() to detect accidentally-concatenated KEY=VALUE pairs, but since LM_API_KEY is a substring of GLM_API_KEY, scanning GLM_API_KEY=abc123 finds LM_API_KEY= at position 1, triggering a false split into G and LM_API_KEY=abc123.

Because sanitize_env_file() is called unconditionally from migrate_config() on every startup, the .env file gets rewritten with the corruption each time.

Affected key pairs (5 total)

Suffix key (skipped)Parent key that triggers false positive
LM_API_KEYGLM_API_KEY
LM_BASE_URLGLM_BASE_URL
LANGFUSE_PUBLIC_KEYHERMES_LANGFUSE_PUBLIC_KEY
LANGFUSE_SECRET_KEYHERMES_LANGFUSE_SECRET_KEY
LANGFUSE_BASE_URLHERMES_LANGFUSE_BASE_URL

Fix

Pre-compute a "suffix traps" set of known keys that are suffixes of other known keys, and skip them during the scan. This eliminates substring false-positives while still correctly splitting truly concatenated lines.

Testing

  • GLM_API_KEY=value → unchanged (was split into G + LM_API_KEY=value)
  • LM_API_KEY=value → unchanged (still works standalone)
  • HERMES_LANGFUSE_PUBLIC_KEY=value → unchanged
  • OPENROUTER_API_KEY=abcGOOGLE_API_KEY=def → correctly split into 2 lines (real concatenation still handled)

Changed files

  • hermes_cli/config.py (modified, +9/-1)

PR #17288: fix(cli): prevent .env sanitizer from splitting GLM_API_KEY by LM_API_KEY suffix

Description (problem / solution / changelog)

Salvage of #17141 by @jackjin1997 onto current main.

Fixes #17138.

_sanitize_env_lines searched every registered env key name as a substring with no word-boundary check. LM_API_KEY= (LM Studio) is a literal suffix of GLM_API_KEY= (Z.AI/GLM), so doctor — or any code path that sanitizes .env — rewrote user files from GLM_API_KEY=... into G\nLM_API_KEY=..., silently killing Z.AI auth. Same shape for GLM_BASE_URL vs LM_BASE_URL.

Fix collects full (start, end) ranges per match and drops ranges fully contained inside a longer overlapping match. Real concatenations (no nesting) still split.

Validation

BeforeAfter
GLM_API_KEY=glm-secretG\nLM_API_KEY=glm-secretunchanged
GLM_API_KEY=glmLM_API_KEY=lm-key (genuine concat)splitssplits
tests/hermes_cli/test_config.py::TestSanitizeEnvLines11 passing13 passing (2 new regressions)

Contributor authorship preserved via rebase-merge.

Changed files

  • hermes_cli/config.py (modified, +14/-5)
  • tests/hermes_cli/test_config.py (modified, +17/-0)

PR #17291: fix(config): prevent _sanitize_env_lines from splitting valid keys via substring match

Description (problem / solution / changelog)

Fixes #17138

Problem

_sanitize_env_lines() uses str.find() to detect concatenated KEY=VALUE pairs. This causes false positives when one known key name is a substring of another:

  • LM_API_KEY= matches at position 1 inside GLM_API_KEY=xxx
  • The sanitizer splits valid entries into garbage: G + LM_API_KEY=xxx
  • Every save_env_value() call triggers this, permanently corrupting .env

Same issue affects GLM_BASE_URL / LM_BASE_URL and potentially any key pair where one name contains another as a substring.

Root Cause Timeline

  • 2026-03-06: GLM support added (GLM_API_KEY, GLM_BASE_URL)
  • 2026-04-13: _sanitize_env_lines added (substring detection via str.find)
  • 2026-04-25: LM Studio integration added (LM_API_KEY, LM_BASE_URL) — substring collision begins

Fix

Two-pass approach with zero new lists to maintain:

Pass 0 (new): Merge split key names. Detect bare uppercase fragments (e.g. G) and merge with the next line when the result is a known key (e.g. GLM_API_KEY=). Uses the existing known_keys set — no additional key registry needed.

Pass 1 (improved): Collect all KEY= matches with (position, key_name_length), then filter out matches embedded inside another key's name via position comparison: other_start < this_start < other_start + other_key_length → skip (substring false positive)

Deterministic (sorted before filtering) and O(matches²) where matches per line is typically 1-3.

Design Advantages

  1. Zero maintenance — uses existing OPTIONAL_ENV_VARS | _EXTRA_ENV_KEYS. No new key lists. Future keys auto-covered.
  2. Deterministic — sorted before filtering. Same input, same output, regardless of set iteration order.
  3. Position-based logic — one comparison per match pair. No character-type heuristics, no startswith scanning, no longest-match ambiguity.
  4. Minimal diff — ~45 lines within a single function. Easy to review and revert.

Test Plan

18 tests in TestSanitizeEnvLines, all passing:

InputResult
GLM_API_KEY=xxxPreserved as-is ✓
GLM_BASE_URL=xxxPreserved as-is ✓
G + newline + LM_API_KEY=xxxMerged → GLM_API_KEY=xxx
G + newline + LM_BASE_URL=xxxMerged → GLM_BASE_URL=xxx
FAL_KEY=abc123FIRECRAWL_API_KEY=def456Split into 2 lines ✓
SOME_RANDOM_TEXT + OPENAI_API_KEY=xxxNo false merge ✓
All 11 existing testsStill passing ✓

Changed files

  • hermes_cli/config.py (modified, +89/-15)
  • tests/hermes_cli/test_config.py (modified, +51/-0)

Code Example

G
LM_API_KEY=
G
LM_BASE_URL=

---

I'm not comfortable sharing these publicly since for some reason it's including very identifiable information for me and my system.

---
RAW_BUFFERClick to expand / collapse

Bug Description

After the last update, when starting Hermes agent from the CLI it seems to be sanitizing the .env file, which splits GLM_API_KEY and GLM_BASE_URL into:

G
LM_API_KEY=
G
LM_BASE_URL=

It appears to be trying to validate LM studio first and breaking the GLM_* convention.

Steps to Reproduce

  1. Add GLM_API_KEY= to .env file
  2. Run hermes
  3. At inference it will fail due to no API key for Z.AI GLM
  4. Exit hermes agent and check .env - GLM_API_KEY= will be split into G\nLM_API_KEY

Expected Behavior

Hermes agent does not sanitize the GLM_API_KEY/GLM_BASE_URL vars and leaves them intact

Actual Behavior

Hermes sanitizes the GLM_API_KEY/GLM_BASE_URL vars into G\nLM_API_KEY and G\nLM_BASE_URL, rendering them invalid for LM Studio and breaking the ZAI/GLM connection

Affected Component

Configuration (config.yaml, .env, hermes setup)

Messaging Platform (if gateway-related)

No response

Debug Report

I'm not comfortable sharing these publicly since for some reason it's including very identifiable information for me and my system.

Operating System

PopOS 24.04 LTS

Python Version

3.11.14

Hermes Version

0.11.0 (2026.4.23)

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

suffix collision in _sanitize_env_lines's known-key splitter.

Mechanism:

  • hermes_cli/config.py registers both LM_API_KEY (for an "LM" provider, auth.py:174) and GLM_API_KEY (zai provider, auth.py:205) — and similarly LM_BASE_URL vs GLM_BASE_URL — in its KNOWN_ENV_VARS table (config.py:1226 / :1242).
  • _sanitize_env_lines (config.py:~3677) walks each .env line scanning for any known-key name as a substring of the line and inserts a newline before each match to "split concatenated KEY=VALUE pairs."
  • LM_API_KEY is a substring of GLM_API_KEY=, so the splitter matches at offset 1, inserts \n before LM_API_KEY, and writes the file back as G\nLM_API_KEY=…. Same logic mangles GLM_BASE_URL → G\nLM_BASE_URL=….
  • Loading python-dotenv then sees G= (empty) and LM_API_KEY=… as separate vars, so os.environ["GLM_API_KEY"] is unset and downstream zai auth falls back to whatever else is configured

Proposed Fix (optional)

require key matches at line start (or anchored after \n), not as raw substrings — e.g., split only when the position is 0 or the preceding char is a non-identifier char ([^A-Za-z0-9_]). Same class of bug applies to any future env var name that's a suffix of another registered 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 issue can be fixed by modifying the _sanitize_env_lines function to require key matches at line start or after a non-identifier character.

Guidance

  • Review the config.py file, specifically the KNOWN_ENV_VARS table and the _sanitize_env_lines function, to understand how environment variable names are being matched and sanitized.
  • Consider modifying the _sanitize_env_lines function to use a regular expression that anchors the match to the start of the line or requires a non-identifier character before the match, to prevent suffix collisions.
  • Test the modified function with the provided example of GLM_API_KEY and GLM_BASE_URL to ensure it no longer splits these variables incorrectly.
  • Verify that the fix does not introduce any new issues with other environment variable names.

Example

import re

# Modified _sanitize_env_lines function
def _sanitize_env_lines(lines):
    known_vars = [re.escape(var) for var in KNOWN_ENV_VARS]
    pattern = r'(?:^|[^A-Za-z0-9_])(' + '|'.join(known_vars) + r')'
    # ... rest of the function remains the same ...

Notes

The proposed fix assumes that the issue is indeed caused by the suffix collision in the _sanitize_env_lines function. If the issue persists after applying this fix, further debugging may be necessary.

Recommendation

Apply the workaround by modifying the _sanitize_env_lines function to require key matches at line start or after a non-identifier character, as this is a targeted fix that 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

hermes - ✅(Solved) Fix [Bug]: On start, Hermes Agent is santizing GLM_API_KEY/GLM_BASE_URL into G\nLM_API_KEY and G\nLM_BASE_URL [5 pull requests, 2 comments, 3 participants]