vault-sync: 2026-10-02 18:00 (jimmy)
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# "cannot import name X from Y" in a long-running process after an update
|
||||
|
||||
> 2026-10-02, **jimmy**. Replaces an earlier note that blamed the gateway for cron-delivery
|
||||
> failures. The real cause is stale modules in a process that was already running when the update
|
||||
> landed. Status: **resolved and understood.**
|
||||
|
||||
## Symptom
|
||||
|
||||
```
|
||||
cannot import name 'UNAUTHORIZED_DM_BEHAVIORS' from 'gateway.config'
|
||||
(C:\Users\fight\AppData\Local\hermes\hermes-agent\gateway\config.py)
|
||||
```
|
||||
|
||||
The cited file defines the name unconditionally at module scope, and its bytecode is current.
|
||||
Fresh processes import it fine; only long-running processes that predate an update fail. Two
|
||||
unrelated modules failed this way in one session:
|
||||
|
||||
| Failing import | Introduced by |
|
||||
|---|---|
|
||||
| `UNAUTHORIZED_DM_BEHAVIORS` from `gateway.config` | `a6e934e0fd` feat(gateway): 'decline' unauthorized-DM behavior |
|
||||
| `tool_errors_since` from `tools.code_execution_rpc` | `3e1bfb2935` fix(execute_code): report in-script tool errors |
|
||||
|
||||
## Root cause
|
||||
|
||||
A process running **before** the update holds pre-update module objects in `sys.modules`. When
|
||||
newly loaded code runs `from <stale module> import <name the update added>`, the import machinery
|
||||
sees the module already cached, looks for the attribute on the stale object, and raises
|
||||
`ImportError: cannot import name …`, reporting the module's `__file__` — which is correct and
|
||||
unchanged. That is exactly why it reads like a file or bytecode defect and is not one.
|
||||
|
||||
Timeline here: session started 08:26:41, update applied 08:45:11, both names arrived with that
|
||||
update. The `execute_code` tool failing with the same shape in the same session confirmed it
|
||||
independently.
|
||||
|
||||
## How it was proven
|
||||
|
||||
- Fresh processes import both names, in every interpreter and PYTHONPATH variant tested, in both
|
||||
the pre-PM and PM environments.
|
||||
- `.pyc` headers match their sources and the bytecode contains both names — no stale bytecode.
|
||||
- No shadowing `gateway/` copy on any `sys.path`; workspace snapshots are byte-identical to the
|
||||
repo (same sha256).
|
||||
- The child environment used by cron delivery (`served_profile_child_env` +
|
||||
`pin_hermes_tree_on_pythonpath`) was replicated exactly — it loads fine.
|
||||
- Every failing attempt was a *manual* job fire, which executes in the calling session's process
|
||||
tree. Running the same job through the gateway delivered natively: `status: ok`,
|
||||
`delivery_error: None`.
|
||||
|
||||
## Rule
|
||||
|
||||
**Restart long-running Hermes processes after an update** — the session that ran it, any
|
||||
long-lived agent process, and the gateway. The update receipt warns about this
|
||||
(`gateway_restart.incomplete` / "mixed sys.modules"); that warning is the real signal, not noise.
|
||||
When you see `cannot import name X from Y` and Y *does* define X, check process start time against
|
||||
the last update before reading any source. There is no in-process recovery: the stale object
|
||||
cannot gain the attribute.
|
||||
|
||||
## Related
|
||||
|
||||
- [[04_projects/trace-review-bot/status]] — the consumer that surfaced it.
|
||||
- [[04_projects/hermes-host-stack/status]] — the update and restart path on this box.
|
||||
Reference in New Issue
Block a user