Summary
Deleting a Hermes profile through any available method (Web UI API, hermes profile delete --yes, manual directory removal) does not actually delete the profile. The profile directory and its gateway process are immediately recreated within seconds.
Steps to Reproduce
- Have multiple profiles with running gateways
- Delete a profile via Web UI:
DELETE /api/hermes/profiles/{name} → returns {"success":true}
- Or via CLI:
hermes profile delete {name} --yes → reports success
- Observe: profile directory reappears, gateway process respawns within seconds
Current Behavior
DELETE /api/hermes/profiles/{name} returns {"success":true} but the profile persists on disk and in hermes profile list
hermes profile delete --yes reports success but the profile persists
- Manually
rm -rf the profile directory and kill -9 the gateway — both are recreated within seconds
- Setting
gatewayAutoStart.enabled = false via config API does not stop existing gateways from respawning
Root Cause Analysis
Three independent mechanisms fight against profile deletion:
1. gateway-runner.ts respawn loop
startGatewayRunManagedInternal() spawns gateways with detached: true and registers an exit handler that respawns the gateway up to MAX_RESPAWN_ATTEMPTS (3) times with a RESPAWN_DELAY_MS (2s) delay. When a profile is deleted and its gateway is killed, the exit handler fires and schedules a respawn. There is no mechanism to tell the runner "this profile is being deleted, don't respawn."
2. ensureProfileGatewaysRunning() in gateway-autostart.ts
Called at Web UI startup (both startRuntimeServicesBeforeListen and startRuntimeServicesAfterListen), this function calls listProfileNamesFromDisk() which scans $HERMES_HOME/profiles/ for directories, then starts a gateway for each discovered profile. Even if gatewayAutoStart.enabled is set to false, the existing gateways that were already spawned by the gateway-runner continue to respawn.
3. container_boot.py s6 reconciliation
On container restart, 02-reconcile-profiles walks $HERMES_HOME/profiles/ and recreates s6 service slots. It reads gateway_state.json — if desired_state is missing, it falls back to gateway_state. Since most profile state files only have gateway_state: "running" (no desired_state field), the reconciler treats them as "should be running" and auto-starts them.
Expected Behavior
Deleting a profile should:
- Stop the associated gateway process
- Remove the profile directory from disk
- Prevent the gateway from being respawned
- The profile should not reappear in
hermes profile list
Proposed Fix Direction
gateway-runner.ts: When a profile is being deleted, clear the respawn state for that profile before killing the gateway
gateway-autostart.ts: Respect gatewayAutoStart.enabled = false for already-running gateways, not just for initial startup
container_boot.py: When desired_state is absent from gateway_state.json, default to stopped rather than falling back to gateway_state (which may be stale)
- Profile delete flow should be atomic: set
desired_state: stopped → kill gateway → clear respawn state → remove directory
Environment
- Hermes Web UI running in Docker (s6-overlay)
- Multiple profiles with per-profile gateways
HERMES_WEB_UI_MANAGED_GATEWAY=1
Summary
Deleting a Hermes profile through any available method (Web UI API,
hermes profile delete --yes, manual directory removal) does not actually delete the profile. The profile directory and its gateway process are immediately recreated within seconds.Steps to Reproduce
DELETE /api/hermes/profiles/{name}→ returns{"success":true}hermes profile delete {name} --yes→ reports successCurrent Behavior
DELETE /api/hermes/profiles/{name}returns{"success":true}but the profile persists on disk and inhermes profile listhermes profile delete --yesreports success but the profile persistsrm -rfthe profile directory andkill -9the gateway — both are recreated within secondsgatewayAutoStart.enabled = falsevia config API does not stop existing gateways from respawningRoot Cause Analysis
Three independent mechanisms fight against profile deletion:
1.
gateway-runner.tsrespawn loopstartGatewayRunManagedInternal()spawns gateways withdetached: trueand registers anexithandler that respawns the gateway up toMAX_RESPAWN_ATTEMPTS(3) times with aRESPAWN_DELAY_MS(2s) delay. When a profile is deleted and its gateway is killed, the exit handler fires and schedules a respawn. There is no mechanism to tell the runner "this profile is being deleted, don't respawn."2.
ensureProfileGatewaysRunning()ingateway-autostart.tsCalled at Web UI startup (both
startRuntimeServicesBeforeListenandstartRuntimeServicesAfterListen), this function callslistProfileNamesFromDisk()which scans$HERMES_HOME/profiles/for directories, then starts a gateway for each discovered profile. Even ifgatewayAutoStart.enabledis set tofalse, the existing gateways that were already spawned by the gateway-runner continue to respawn.3.
container_boot.pys6 reconciliationOn container restart,
02-reconcile-profileswalks$HERMES_HOME/profiles/and recreates s6 service slots. It readsgateway_state.json— ifdesired_stateis missing, it falls back togateway_state. Since most profile state files only havegateway_state: "running"(nodesired_statefield), the reconciler treats them as "should be running" and auto-starts them.Expected Behavior
Deleting a profile should:
hermes profile listProposed Fix Direction
gateway-runner.ts: When a profile is being deleted, clear the respawn state for that profile before killing the gatewaygateway-autostart.ts: RespectgatewayAutoStart.enabled = falsefor already-running gateways, not just for initial startupcontainer_boot.py: Whendesired_stateis absent fromgateway_state.json, default tostoppedrather than falling back togateway_state(which may be stale)desired_state: stopped→ kill gateway → clear respawn state → remove directoryEnvironment
HERMES_WEB_UI_MANAGED_GATEWAY=1