baecc840e5
A Windows user reported every startup dying with `ImportError: cannot import name 'TODO_INJECTION_HEADER' from 'tools.todo_tool'`. The symbol exists on main; their tree had the new `agent/context_compressor.py` (which imports it at module level) alongside a pre-update `tools/todo_tool.py`. The post-update guard missed it. `_validate_critical_files_syntax` only py_compiles files, and every file in a skewed tree parses fine — it is the combination that is broken. The guard reported success and the update completed over an install that could not start. The ZIP-update path (Windows-only, used when git file I/O is broken) is where the skew comes from: its copy loop replaces top-level entries one at a time in `os.listdir` order, so `agent/` lands at index 13 and `tools/` at index 66. Any failure between them leaves exactly this mismatch — and that path had no post-copy validation or rollback at all. - Add `_validate_critical_modules_import`: imports the four startup modules in a subprocess (~0.4s) so cross-module breakage is caught. Non-import errors (config/env) are ignored; a probe that cannot spawn is non-fatal so we never block an update on our own tooling. - Run it after the syntax guard on the git path, reusing the existing auto-rollback. - Run it on the ZIP path after dependency install (so a genuinely-new requirement is not misreported as a partial copy), and make the ZIP failure message state the install may be half-updated. - Add `partial_update_hint()` and print it under "Failed to initialize agent", so users see "re-run hermes update" instead of a bare ImportError. Stays silent for ModuleNotFoundError and third-party imports, which need different remediation. Verified by simulating the exact skew: the syntax guard returns ok=True while the import guard returns the user's error verbatim.