hermes - 💡(How to fix) Fix Bug: `execute_code` RPC threads start with empty context — ContextVars set by the agent don't propagate [1 comments, 1 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#28759Fetched 2026-05-20 04:02:11
View on GitHub
Comments
1
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4closed ×1commented ×1

The two threading.Thread(target=...) spawns inside tools/code_execution_tool.py::_execute_local (the RPC-handler threads that bridge the sandboxed Python child's hermes_tools.terminal(...) calls back to the parent's tool runtime) start with empty Python contextvars. Any ContextVar the agent set in its parent task — including the ones Hermes itself sets via tools.memory_tool.current_user_email — is invisible to those threads. Code paths in the parent that depend on that ContextVar (e.g., per-user memory routing, per-user audit logs) silently misbehave when invoked through the RPC handlers.

Error Message

Silent. The misbehavior is "ContextVars set by the parent are silently missing in the RPC handlers" — there's no exception, no warning, no log. We discovered this when investigating why per-user memory writes from inside execute_code were landing in the wrong directory.

Root Cause

threading.Thread(target=fn).start() in CPython does NOT carry the spawning task's contextvars.Context into the new thread — the thread runs with a fresh empty Context. This is by design at the language level: asyncio.Task propagates context implicitly, but raw threading.Thread does not. The fix is to wrap the thread target with contextvars.copy_context().run(...):

import contextvars

ctx = contextvars.copy_context()
threading.Thread(target=lambda: ctx.run(original_target)).start()

This carries the parent task's context (all ContextVars, including ones from third-party plugins) into the spawned thread. The standard library calls this pattern out explicitly in the contextvars docs.

Fix Action

Workaround

We currently apply this fix as a Hermes source patch (patches/75-rpc-thread-contextvar-propagation.sh) that re-applies on every Hermes upgrade. Happy to upstream the patch as a PR if the fix above is acceptable. Tests would cover both cases: a ContextVar set in the parent task is visible in the RPC thread, AND the fix is a no-op when no ContextVar is set.

Code Example

import contextvars

ctx = contextvars.copy_context()
threading.Thread(target=lambda: ctx.run(original_target)).start()

---

ctx = contextvars.copy_context()
t = threading.Thread(target=lambda: ctx.run(original_target_fn))
RAW_BUFFERClick to expand / collapse

Bug: execute_code RPC threads start with empty context — ContextVars set by the agent don't propagate

Summary

The two threading.Thread(target=...) spawns inside tools/code_execution_tool.py::_execute_local (the RPC-handler threads that bridge the sandboxed Python child's hermes_tools.terminal(...) calls back to the parent's tool runtime) start with empty Python contextvars. Any ContextVar the agent set in its parent task — including the ones Hermes itself sets via tools.memory_tool.current_user_email — is invisible to those threads. Code paths in the parent that depend on that ContextVar (e.g., per-user memory routing, per-user audit logs) silently misbehave when invoked through the RPC handlers.

Reproduction

In a Hermes session, set tools.memory_tool.current_user_email in the inbound-message handler (this is how the Slack adapter binds per-user memory). Then have the agent call execute_code with a Python snippet that calls hermes_tools.terminal("memory_tool list") (or any tool that reads the email ContextVar). The list comes back empty — the memory_tool's RPC handler runs in the RPC thread, which has no email ContextVar set, so the per-user memory directory lookup falls back to the default scope.

Same shape applies to any future ContextVar a plugin or fork sets.

Root cause

threading.Thread(target=fn).start() in CPython does NOT carry the spawning task's contextvars.Context into the new thread — the thread runs with a fresh empty Context. This is by design at the language level: asyncio.Task propagates context implicitly, but raw threading.Thread does not. The fix is to wrap the thread target with contextvars.copy_context().run(...):

import contextvars

ctx = contextvars.copy_context()
threading.Thread(target=lambda: ctx.run(original_target)).start()

This carries the parent task's context (all ContextVars, including ones from third-party plugins) into the spawned thread. The standard library calls this pattern out explicitly in the contextvars docs.

Affected lines

tools/code_execution_tool.py::_execute_local (in ~/.hermes/hermes-agent/). Two threading.Thread(...) calls — the stdin pump and the stdout/stderr pump that bridge the subprocess's stdio to the RPC channel. Both currently run with target=fn (no context wrapping).

Severity

Silent. The misbehavior is "ContextVars set by the parent are silently missing in the RPC handlers" — there's no exception, no warning, no log. We discovered this when investigating why per-user memory writes from inside execute_code were landing in the wrong directory.

Proposed fix

ctx = contextvars.copy_context()
t = threading.Thread(target=lambda: ctx.run(original_target_fn))

Applied to both threading.Thread calls in _execute_local. Idempotent for users who don't rely on ContextVars (the wrap is a no-op in that case).

Workaround

We currently apply this fix as a Hermes source patch (patches/75-rpc-thread-contextvar-propagation.sh) that re-applies on every Hermes upgrade. Happy to upstream the patch as a PR if the fix above is acceptable. Tests would cover both cases: a ContextVar set in the parent task is visible in the RPC thread, AND the fix is a no-op when no ContextVar is set.

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 - 💡(How to fix) Fix Bug: `execute_code` RPC threads start with empty context — ContextVars set by the agent don't propagate [1 comments, 1 participants]