Skip to content

feat(process): Hermes processes are positively identified and guaranteed dead at update time - #90777

Merged
teknium1 merged 2 commits into
mainfrom
feat/process-identity-ledger
Aug 20, 2026
Merged

feat(process): Hermes processes are positively identified and guaranteed dead at update time#90777
teknium1 merged 2 commits into
mainfrom
feat/process-identity-ledger

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Every long-lived Hermes process is now positively identifiable — tagged at spawn, self-registered in a machine spawn ledger, and (on Windows) bound into a kill-on-close job — so hermes update reaps leaked backends by proof instead of PPID archaeology, and Hermes reliably dies when we update.

Follow-through on the 2026-08-20 Windows incident pair: #90497 stopped the leak factory (ownership-file corruption), #90746 cleaned up hand-off leaks with a contract-gated heuristic. This PR replaces guesswork with identity so the whole class stays dead.

The three layers

  1. Spawn tags — spawners stamp children with HERMES_SPAWN=v1:<install>:<purpose>:<spawner_pid>:<spawner_create>. The Desktop stamps its backend spawns (parent-process-identity.ts); the Python side also accepts legacy HERMES_PARENT_PID + winms: markers as lineage, so it works with every Desktop version.
  2. Spawn ledger (spawn-ledger.json at the machine Hermes root) — serve/dashboard backends and the gateway self-register (pid, create_time, purpose, spawner) at startup. The (pid, create_time) pair defeats PID reuse. Dead entries are pruned on every write; a corrupt ledger is quarantined to .corrupt, never rewritten blind (same contract as fix(desktop): corrupt backend-ownership.json no longer erases records of live backends (#89298) #90497 / Desktop-spawned hermes serve backends leak unbounded (28 live instances); leaked backends' MCP OAuth storms browser login tabs #89298).
  3. Windows job objects — backends and the gateway self-attach to a job with KILL_ON_JOB_CLOSE, so their whole child tree (launcher→worker chains holding .pyd locks) dies atomically with them. BREAKAWAY_OK + SILENT_BREAKAWAY_OK preserve every existing CREATE_BREAKAWAY_FROM_JOB escape (gateway respawn watcher, detached relaunch).

Updater wiring

hermes update gains a positive-identity rung that runs FIRST, in any update context (no hand-off contract needed): _ledger_reapable_backend_pids() reaps venv holders the ledger proves are orphaned backends — reapable purpose (serve/dashboard/gateway, never interactive) AND recorded spawner provably dead. Ledger-unknown holders fall through to the existing rungs (gateway pause → orphan reap → hand-off reap → refuse), which remain untouched as the compatibility path for processes started by pre-ledger versions.

Changes

  • hermes_cli/process_identity.py (new): tags, ledger, job-object attach — all best-effort/fail-safe, never blocks startup or update.
  • hermes_cli/web_server.py, gateway/run.py: self-registration + job attach at the two long-lived entry points.
  • apps/desktop/electron/parent-process-identity.ts: spawnTag() + HERMES_SPAWN in the watchdog env.
  • hermes_cli/update_cmd.py: _ledger_reapable_backend_pids() + the new first rung; hermes_cli/main.py: lazy-export.

Validation

Result
New test_process_identity.py 22/22 pass (tags, ledger prune/quarantine/PID-reuse, tristate spawner probe, updater rung)
Sabotage (ledger rung neutered) positive test FAILS as intended; 22/22 on restore
Regression: hand-off + orphan reap suites 45/45 combined pass
ruff (all touched py files) clean

Infographic

Every process accounted for

…ledger, Windows job-object self-attach

Every long-lived Hermes process is now positively identifiable so reapers
never have to guess lineage from PPID archaeology or cmdline shape:

- hermes_cli/process_identity.py (new): HERMES_SPAWN tag build/parse,
  spawn-ledger.json self-registration keyed on (pid, create_time) — PID
  reuse cannot forge the pair — with #89298-style corrupt-file quarantine,
  and a kill-on-close job-object self-attach (BREAKAWAY_OK preserved for
  the existing CREATE_BREAKAWAY_FROM_JOB escape hatches).
- serve/dashboard (web_server.py) and the gateway entry point register
  themselves at startup and attach to the job; Desktop legacy
  HERMES_PARENT_PID/winms marker reused as spawner identity so lineage
  works with every Desktop version.
- Desktop stamps HERMES_SPAWN on backend spawns (parent-process-identity.ts).
- hermes update gets a positive-identity rung ahead of the heuristic ones:
  _ledger_reapable_backend_pids reaps holders the ledger PROVES are orphaned
  backends (purpose reapable + recorded spawner provably dead) in ANY update
  context. Ledger-unknown holders fall through to the existing rungs.

22 new tests, sabotage-verified.
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 77dcae0 — test(desktop): parent watchdog env now carries HERMES_SPAWN

⚠️ Warnings

CI timings · View report · View job

Wall time 5m35s vs 3m50s (+45.7%). 20 job(s) slower, 16 faster, 2 unchanged.

  • Python tests / Run tests slice 1/12: +144.0s
  • JS & TS checks / apps/shared / check: +65.0s
  • Python tests / Run tests slice 2/12: -45.0s
  • JS & TS checks / apps/desktop / check:test:ui:shard-3of3: +45.0s
  • Python tests / Run tests slice 12/12: -30.0s

OSV vulnerability scan · View job

7 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard platform/windows Native Windows-specific behavior or breakage area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 20, 2026
…exact-shape assertions

The two deepEqual tests pin the exact env object, so the new spawn tag
field made them red. Assert the tag in both shapes and add direct
spawnTag() coverage (winms-derived seconds, dash fallback, non-winms
markers rejected).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants