Skip to content

fix(backup): hermes import never overwrites volatile gateway runtime state (NS-501/508) - #48243

Merged
benbarclay merged 1 commit into
mainfrom
fix/ns501-import-skip-runtime-state
Jun 18, 2026
Merged

fix(backup): hermes import never overwrites volatile gateway runtime state (NS-501/508)#48243
benbarclay merged 1 commit into
mainfrom
fix/ns501-import-skip-runtime-state

Conversation

@benbarclay

Copy link
Copy Markdown
Collaborator

Infographic

safe-import

Summary

This is the second half of NS-501 (filed separately as NS-508, "Agent disconnected from Nous portal after import, stuck on 'starting'"). The upload-side 502 was fixed in #47663; this fixes the import-breaks-the-instance half.

hermes import wrote every file from the backup zip over the target home wholesale — the only thing it refused was a path-traversal. On a hosted instance this clobbered gateway_state.json with the source machine's last recorded run/desired state. The container-boot reconciler (container_boot._read_desired_state) only auto-starts a gateway whose recorded state is running; a backup taken from a laptop where the gateway was stopped (or carrying a stale/foreign value) overwrote the container's own state and left the gateway stuck "starting"/"cooking", disconnected from the Nous portal.

Root cause

hermes_cli/backup.py:run_import() extracted each zip member straight onto disk. The volatile runtime files that are namespaced to the machine/container the backup was taken on were restored along with everything else:

  • gateway_state.json — drives the boot reconciler's auto-start decision. The actual NS-508 breaker.
  • gateway.pid / cron.pid / gateway.lock / processes.json — reference PIDs and locks in the source machine's process namespace; a numerically-equal PID in the new environment is a different process.

The backup creates these too (they aren't in the backup-side _EXCLUDED_NAMES), and older backups predate any exclusion, so the correct and complete place to filter is on import.

Fix

Add _IMPORT_SKIP_NAMES = {gateway_state.json, gateway.pid, cron.pid, gateway.lock, processes.json} and skip them by basename in run_import, so both the root profile and named profiles (profiles/<name>/gateway_state.json) keep the target's own runtime state. The import summary reports which files were preserved. This mirrors exactly what container_boot._STALE_RUNTIME_FILES already sweeps on every container boot.

No hosted detection, no lifecycle/restart machinery — a single general import-safety rule that also protects a user restoring a backup onto any live machine.

Tests (proven to fail without the fix)

Four new behavioral tests in tests/hermes_cli/test_backup.py, each confirmed RED on main and GREEN with the fix:

  • test_preserves_live_gateway_state — live gateway_state.json wins over the backup's
  • test_does_not_seed_gateway_state_when_absent — a foreign state is dropped, never seeded
  • test_preserves_per_profile_gateway_state — basename match covers named profiles
  • test_preserves_runtime_pid_and_process_files — pid/lock/process files preserved

Full module: 114 passed. Plus a real end-to-end run of the actual hermes import CLI against a temp HERMES_HOME: the live root + profile gateway_state.json and gateway.pid were preserved while config.yaml, .env, skills, and profile config were restored normally.

Fixes NS-508 (second half of NS-501).

…state (NS-501)

Importing a backup wrote every file from the zip over the target home
wholesale. On a hosted instance this clobbered gateway_state.json with the
source machine's last recorded run/desired state — driving the container-boot
reconciler (container_boot._read_desired_state, which only auto-starts a
gateway whose state is "running") off stale/foreign state and leaving the
gateway stuck "starting", disconnected from the Nous portal.

Add _IMPORT_SKIP_NAMES (gateway_state.json, gateway.pid, cron.pid,
gateway.lock, processes.json) and skip them by basename in run_import, so both
the root profile and named profiles preserve the target's own runtime state.
This mirrors what container_boot._STALE_RUNTIME_FILES already sweeps on every
container boot, and protects against older backups that predate the
backup-side exclusions. The import summary reports which files were preserved.

This is the second half of NS-501 (filed separately as NS-508): the upload
502 was fixed in #47663; this fixes the import-breaks-the-instance half.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: fix/ns501-import-skip-runtime-state vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10994 on HEAD, 10996 on base (✅ -2)

🆕 New issues (1):

Rule Count
invalid-assignment 1
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`

✅ Fixed issues (2):

Rule Count
unresolved-attribute 2
First entries
run_agent.py:2941: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`

Unchanged: 5760 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists labels Jun 18, 2026
@benbarclay
benbarclay merged commit 9c3c5da into main Jun 18, 2026
35 checks passed
@benbarclay
benbarclay deleted the fix/ns501-import-skip-runtime-state branch June 18, 2026 05:27
xyshanren pushed a commit to xyshanren/hermes-agent-cn that referenced this pull request Jun 25, 2026
…state (NS-501) (NousResearch#48243)

Importing a backup wrote every file from the zip over the target home
wholesale. On a hosted instance this clobbered gateway_state.json with the
source machine's last recorded run/desired state — driving the container-boot
reconciler (container_boot._read_desired_state, which only auto-starts a
gateway whose state is "running") off stale/foreign state and leaving the
gateway stuck "starting", disconnected from the Nous portal.

Add _IMPORT_SKIP_NAMES (gateway_state.json, gateway.pid, cron.pid,
gateway.lock, processes.json) and skip them by basename in run_import, so both
the root profile and named profiles preserve the target's own runtime state.
This mirrors what container_boot._STALE_RUNTIME_FILES already sweeps on every
container boot, and protects against older backups that predate the
backup-side exclusions. The import summary reports which files were preserved.

This is the second half of NS-501 (filed separately as NS-508): the upload
502 was fixed in NousResearch#47663; this fixes the import-breaks-the-instance half.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…state (NS-501) (NousResearch#48243)

Importing a backup wrote every file from the zip over the target home
wholesale. On a hosted instance this clobbered gateway_state.json with the
source machine's last recorded run/desired state — driving the container-boot
reconciler (container_boot._read_desired_state, which only auto-starts a
gateway whose state is "running") off stale/foreign state and leaving the
gateway stuck "starting", disconnected from the Nous portal.

Add _IMPORT_SKIP_NAMES (gateway_state.json, gateway.pid, cron.pid,
gateway.lock, processes.json) and skip them by basename in run_import, so both
the root profile and named profiles preserve the target's own runtime state.
This mirrors what container_boot._STALE_RUNTIME_FILES already sweeps on every
container boot, and protects against older backups that predate the
backup-side exclusions. The import summary reports which files were preserved.

This is the second half of NS-501 (filed separately as NS-508): the upload
502 was fixed in NousResearch#47663; this fixes the import-breaks-the-instance half.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…state (NS-501) (NousResearch#48243)

Importing a backup wrote every file from the zip over the target home
wholesale. On a hosted instance this clobbered gateway_state.json with the
source machine's last recorded run/desired state — driving the container-boot
reconciler (container_boot._read_desired_state, which only auto-starts a
gateway whose state is "running") off stale/foreign state and leaving the
gateway stuck "starting", disconnected from the Nous portal.

Add _IMPORT_SKIP_NAMES (gateway_state.json, gateway.pid, cron.pid,
gateway.lock, processes.json) and skip them by basename in run_import, so both
the root profile and named profiles preserve the target's own runtime state.
This mirrors what container_boot._STALE_RUNTIME_FILES already sweeps on every
container boot, and protects against older backups that predate the
backup-side exclusions. The import summary reports which files were preserved.

This is the second half of NS-501 (filed separately as NS-508): the upload
502 was fixed in NousResearch#47663; this fixes the import-breaks-the-instance half.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…state (NS-501) (NousResearch#48243)

Importing a backup wrote every file from the zip over the target home
wholesale. On a hosted instance this clobbered gateway_state.json with the
source machine's last recorded run/desired state — driving the container-boot
reconciler (container_boot._read_desired_state, which only auto-starts a
gateway whose state is "running") off stale/foreign state and leaving the
gateway stuck "starting", disconnected from the Nous portal.

Add _IMPORT_SKIP_NAMES (gateway_state.json, gateway.pid, cron.pid,
gateway.lock, processes.json) and skip them by basename in run_import, so both
the root profile and named profiles preserve the target's own runtime state.
This mirrors what container_boot._STALE_RUNTIME_FILES already sweeps on every
container boot, and protects against older backups that predate the
backup-side exclusions. The import summary reports which files were preserved.

This is the second half of NS-501 (filed separately as NS-508): the upload
502 was fixed in NousResearch#47663; this fixes the import-breaks-the-instance half.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…state (NS-501) (NousResearch#48243)

Importing a backup wrote every file from the zip over the target home
wholesale. On a hosted instance this clobbered gateway_state.json with the
source machine's last recorded run/desired state — driving the container-boot
reconciler (container_boot._read_desired_state, which only auto-starts a
gateway whose state is "running") off stale/foreign state and leaving the
gateway stuck "starting", disconnected from the Nous portal.

Add _IMPORT_SKIP_NAMES (gateway_state.json, gateway.pid, cron.pid,
gateway.lock, processes.json) and skip them by basename in run_import, so both
the root profile and named profiles preserve the target's own runtime state.
This mirrors what container_boot._STALE_RUNTIME_FILES already sweeps on every
container boot, and protects against older backups that predate the
backup-side exclusions. The import summary reports which files were preserved.

This is the second half of NS-501 (filed separately as NS-508): the upload
502 was fixed in NousResearch#47663; this fixes the import-breaks-the-instance half.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…state (NS-501) (NousResearch#48243)

Importing a backup wrote every file from the zip over the target home
wholesale. On a hosted instance this clobbered gateway_state.json with the
source machine's last recorded run/desired state — driving the container-boot
reconciler (container_boot._read_desired_state, which only auto-starts a
gateway whose state is "running") off stale/foreign state and leaving the
gateway stuck "starting", disconnected from the Nous portal.

Add _IMPORT_SKIP_NAMES (gateway_state.json, gateway.pid, cron.pid,
gateway.lock, processes.json) and skip them by basename in run_import, so both
the root profile and named profiles preserve the target's own runtime state.
This mirrors what container_boot._STALE_RUNTIME_FILES already sweeps on every
container boot, and protects against older backups that predate the
backup-side exclusions. The import summary reports which files were preserved.

This is the second half of NS-501 (filed separately as NS-508): the upload
502 was fixed in NousResearch#47663; this fixes the import-breaks-the-instance half.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants