fix: sync client.api_key during UnicodeEncodeError ASCII recovery#10090
Merged
Conversation
The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
wpsl5168
added a commit
to wpsl5168/hermes-agent
that referenced
this pull request
Apr 15, 2026
…ream * fix: sync client.api_key during UnicodeEncodeError ASCII recovery (NousResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing * fix: stale agent timeout, uv venv detection, empty response after tools, compression model fallback (NousResearch#9051, NousResearch#8620, NousResearch#9400) (NousResearch#10093) Four independent fixes: 1. Reset activity timestamp on cached agent reuse (NousResearch#9051) When the gateway reuses a cached AIAgent for a new turn, the _last_activity_ts from the previous turn (possibly hours ago) carried over. The inactivity timeout handler immediately saw the agent as idle for hours and killed it. Fix: reset _last_activity_ts, _last_activity_desc, and _api_call_count when retrieving an agent from the cache. 2. Detect uv-managed virtual environments (NousResearch#8620 sub-issue 1) The systemd unit generator fell back to sys.executable (uv's standalone Python) when running under 'uv run', because sys.prefix == sys.base_prefix. The generated ExecStart pointed to a Python binary without site-packages. Fix: check VIRTUAL_ENV env var before falling back to sys.executable. uv sets VIRTUAL_ENV even when sys.prefix doesn't reflect the venv. 3. Nudge model to continue after empty post-tool response (NousResearch#9400) Weaker models sometimes return empty after tool calls. The agent silently abandoned the remaining work. Fix: append assistant('(empty)') + user nudge message and retry once. Resets after each successful tool round. 4. Compression model fallback on permanent errors (NousResearch#8620 sub-issue 4) When the default summary model (gemini-3-flash) returns 503 'model_not_found' on custom proxies, the compressor entered a 600s cooldown, leaving context growing unbounded. Fix: detect permanent model-not-found errors (503, 404, 'model_not_found', 'no available channel') and fall back to using the main model for compression instead of entering cooldown. One-time fallback with immediate retry. Test plan: 40 compressor tests + 97 gateway/CLI tests + 9 venv tests pass * fix(cli): defensive subparser routing for argparse bpo-9338 (NousResearch#10113) On some Python versions, argparse fails to route subcommand tokens when the parent parser has nargs='?' optional arguments (--continue). The symptom: 'hermes model' produces 'unrecognized arguments: model' even though 'model' is a registered subcommand. Fix: when argv contains a token matching a known subcommand, set subparsers.required=True to force deterministic routing. If that fails (e.g. 'hermes -c model' where 'model' is consumed as the session name for --continue), fall back to the default optional-subparsers behaviour. Adds 13 tests covering all key argument combinations. Reported via user screenshot showing the exact error on an installed version with the model subcommand listed in usage but rejected at parse time. * feat(doctor): add Command Installation check for hermes bin symlink hermes doctor now checks whether the ~/.local/bin/hermes symlink exists and points to the correct venv entry point. With --fix, it creates or repairs the symlink automatically. Covers: - Missing symlink at ~/.local/bin/hermes (or $PREFIX/bin on Termux) - Symlink pointing to wrong target - Missing venv entry point (venv/bin/hermes or .venv/bin/hermes) - PATH warning when ~/.local/bin is not on PATH - Skipped on Windows (different mechanism) Addresses user report: 'python -m hermes_cli.main doesn't have an option to fix the local bin/install' 10 new tests covering all scenarios. * fix(cli): restore messaging toolset for gateway platforms * feat: auto session summaries - generate structured summaries on session end, inject into future sessions * fix: add summary column to SCHEMA_SQL for fresh DB installs * feat: auto memory extraction on session end Adds _spawn_auto_extraction() that runs in a background thread after session ends. Uses LLM to analyze conversation and extract durable facts (preferences, corrections, environment details) into memory. - Dedup against existing memory state - Structured JSON operations (add/replace/remove) - Configurable via memory.auto_extraction config - Minimum 3 meaningful turns required to trigger - Capped at 10 operations per session Part of the Memory Architecture Upgrade initiative. * feat: elastic hot/cold memory with FTS5 search Phase 3 of memory architecture upgrade: - DB schema v7→v8: new memory_entries table with FTS5 full-text search - SessionDB: 7 new cold memory methods (add/get/search/remove/list/touch/count) - MemoryStore: hot/cold architecture with archive (hot→cold), promote (cold→hot), add_to_cold, search_cold methods - memory_tool: 3 new actions (search, archive, promote) for cold storage - Auto extraction fallback: when hot memory full, auto-stores to cold - MEMORY_GUIDANCE updated with cold storage instructions - All integration tests passing * fix: always write .clean_shutdown on graceful stop to prevent session loss Previously the marker was skipped when drain timed out, causing suspend_recently_active() to mark sessions on next startup, which led to unwanted auto-resets that lost conversation history. Drain timeout means agents were slow, not that sessions are stuck. The stuck-loop detector handles genuinely stuck sessions independently. * feat: memory system upgrade - sqlite-vec, hybrid search, Auto Dream P0: Fix session reset on gateway restart (always write .clean_shutdown) P1a: sqlite-vec v0.1.9 + embedding_client.py (OpenRouter text-embedding-3-small) P1b: session_search hybrid FTS5+vec, prioritize cached summaries P1c: cold memory embedding integration P1d: hot memory expansion (4400/2750 chars) P2: Auto Dream trigger (OR logic: >=24h or >=5 sessions) - _check_auto_dream() in run_agent.py - Dream A cron changed from weekly to daily fallback - system_meta tracks last_dream_at + sessions_since_dream Schema: v8 -> v9 (embeddings, system_meta, embeddings_vec tables) * feat(embedding): migrate to Ollama local embedding with OpenRouter fallback - Rewrite embedding_client.py for dual-backend architecture: Ollama (nomic-embed-text, 768-dim) as primary, OpenRouter as fallback - Add get_query_embedding() with 'search_query:' prefix for retrieval - Dynamic EMBEDDING_DIM detection based on active backend - hermes_state.py: read dimensions from embedding_client instead of hardcoding - memory_tool.py, session_search_tool.py: use get_query_embedding() for searches - Add scripts/backfill_all_embeddings.py for historical data migration Zero-cost local embeddings, ~0.8s per call, no API rate limits. --------- Co-authored-by: Teknium <[email protected]> Co-authored-by: Teknium <[email protected]> Co-authored-by: Ubuntu <davida@boombox.kh1kbv5xmjqelbtffzsvgoyjie.cx.internal.cloudapp.net>
ulasbilgen
pushed a commit
to ulasbilgen/hermes-adhd-agent
that referenced
this pull request
May 1, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
aj-nt
pushed a commit
to aj-nt/hermes-agent
that referenced
this pull request
May 1, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
Egavasyug
pushed a commit
to Egavasyug/hermes-agent
that referenced
this pull request
Jun 10, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
liuchanchen
pushed a commit
to liuchanchen/hermes-agent
that referenced
this pull request
Jul 3, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
kulikman
pushed a commit
to kulikman/hermes-agent
that referenced
this pull request
Jul 16, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
waym0reom3ga
added a commit
to waym0reom3ga/autolycus-agent
that referenced
this pull request
Jul 21, 2026
…usResearch#10090) The existing recovery block sanitized self.api_key and self._client_kwargs['api_key'] but did not update self.client.api_key. The OpenAI SDK stores its own copy of api_key and reads it dynamically via the auth_headers property on every request. Without this fix, the retry after sanitization would still send the corrupted key in the Authorization header, causing the same UnicodeEncodeError. The bug manifests when an API key contains Unicode lookalike characters (e.g. ʋ U+028B instead of v) from copy-pasting out of PDFs, rich-text editors, or web pages with decorative fonts. httpx hard-encodes all HTTP headers as ASCII, so the non-ASCII char in the Authorization header triggers the error. Adds TestApiKeyClientSync with two tests verifying: - All three key locations are synced after sanitization - Recovery handles client=None (pre-init) without crashing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes an incomplete recovery path for non-ASCII characters in API keys. The existing UnicodeEncodeError recovery (added for #6843) sanitizes
self.api_keyandself._client_kwargs['api_key']but did not updateself.client.api_key. The OpenAI SDK stores its own copy and reads it via theauth_headersproperty on every request — so the retry after sanitization still sent the corrupted key.Root cause
API keys containing Unicode lookalike characters (e.g. ʋ U+028B instead of v, from copy-pasting out of PDFs or rich-text editors) cause
UnicodeEncodeError: 'ascii' codec can't encode characterbecause httpx hard-encodes all HTTP headers as ASCII. The recovery block stripped the bad chars from the agent's state but not from the live client object.Changes
self.api_keyand_client_kwargs, also setself.client.api_key = _clean_key(with null safety checks)TestApiKeyClientSyncclass with 2 tests verifying all three key locations are synced and thatclient=Nonedoesn't crashReported by
Community user kshitij (@kshitijk4poor) — webhook-triggered routines failed with
UnicodeEncodeErrorat position 153 (exactly in the Authorization header range) while manual runs worked (different env var source).Test plan