Skip to content

fix(gateway): default restart_drain_timeout to 0 to kill systemd crash loop - #54066

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-4a07a857
Jun 28, 2026
Merged

fix(gateway): default restart_drain_timeout to 0 to kill systemd crash loop#54066
teknium1 merged 1 commit into
mainfrom
hermes/hermes-4a07a857

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

A gateway restart now interrupts in-flight agents immediately instead of holding the process open for a grace window, which kills the systemd crash-loop reported in #31981.

Root cause: restart_drain_timeout (the gateway's own drain timer) and systemd's TimeoutStopSec (the kill timer) were two independently-set values. On a stale unit where TimeoutStopSec < drain, systemd SIGKILLed the gateway mid-cleanup, before it could remove its PID/lock file. The next startup then saw the stale lock, exited with "another gateway instance is already running", and under Restart=on-failure looped forever (one reporter's diag log hit 63 MB over 13 days).

Setting the drain default to 0 makes the mismatch structurally impossible: with drain 0 the generated unit gets TimeoutStopSec=90 against a near-instant drain, so systemd never kills mid-cleanup. The contract is now explicit — restart the gateway, in-flight work stops. A grace window large enough to "save" a long agent turn would have to outlast an unbounded task, which is impossible.

Changes

  • hermes_cli/config.py: restart_drain_timeout default 180 → 0; comment rewritten to state the contract and the timer-coupling rationale.
  • cli-config.yaml.example: doc comment updated to match (60 → 0).
  • gateway/run.py: stale-unit warning suggested hermes gateway service install --replace — that subcommand does not exist. Corrected to hermes gateway install --force.
  • tests/gateway/test_gateway_shutdown.py: the one test that depended on a positive default drain now sets an explicit 5.0 to cover the opt-in grace-window path.

Notes:

  • DEFAULT_GATEWAY_RESTART_DRAIN_TIMEOUT derives from DEFAULT_CONFIG, so it tracks the new default automatically — single source of truth.
  • The unit-generation tests are invariant-based (max(60, DEFAULT) + 30), so they carried the change with no edits and now assert TimeoutStopSec=90.
  • parse_restart_drain_timeout("0") returns 0.0 cleanly (no coercion-to-default edge with the new 0 default).

Validation

Before After
Default drain 180s grace window on every restart 0 — interrupt immediately
systemd kill-timer vs drain could be SIGKILLed mid-cleanup (stale unit) 90s ≫ near-instant drain; race impossible
Stale-unit warning command hermes gateway service install --replace (invalid) hermes gateway install --force
Crash loop on stale unit infinite cannot form

Targeted suites green: test_gateway_service (175), test_restart_drain (21), test_gateway_shutdown, test_shutdown_forensics — 196 tests.

Closes #31981.

Infographic

gateway-restart-crash-loop

…h loop

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes #31981

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Clean config change that defaults restart_drain_timeout to 0 to prevent systemd crash loops. The old 180s default was causing SIGKILL-mid-cleanup races when TimeoutStopSec was shorter than the drain window. The new default (0 = interrupt immediately) sidesteps both the unbounded-task problem and the SIGKILL race. Tests are updated to explicitly opt into a grace window when needed. Well-documented with clear rationale in the code comments.


Reviewed by Hermes Agent

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles P1 High — major feature broken, no workaround sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jun 28, 2026
@teknium1
teknium1 merged commit c9df4bc into main Jun 28, 2026
30 checks passed
@teknium1
teknium1 deleted the hermes/hermes-4a07a857 branch June 28, 2026 08:14
pai-scaffolde pushed a commit to pai-scaffolde/hermes-agent that referenced this pull request Jun 28, 2026
…h loop (NousResearch#54066)

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (NousResearch#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes NousResearch#31981
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…h loop (NousResearch#54066)

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (NousResearch#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes NousResearch#31981
Jasper6439 pushed a commit to Jasper6439/hermes-agent that referenced this pull request Jul 5, 2026
…h loop (NousResearch#54066)

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (NousResearch#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes NousResearch#31981
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…h loop (NousResearch#54066)

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (NousResearch#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes NousResearch#31981
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…h loop (NousResearch#54066)

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (NousResearch#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes NousResearch#31981
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…h loop (NousResearch#54066)

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (NousResearch#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes NousResearch#31981
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…h loop (NousResearch#54066)

A restart now interrupts in-flight agents immediately rather than holding
the gateway open for a grace window. The previous 180s default coupled two
independently-set timers: the gateway's own drain timer and systemd's
TimeoutStopSec. On a stale unit where TimeoutStopSec < drain, systemd
SIGKILLed the gateway mid-cleanup, leaving a stale lock that made the next
startup exit immediately ('already running') — an infinite crash loop under
Restart=on-failure (NousResearch#31981).

Setting drain to 0 makes the mismatch structurally impossible: with drain 0
the generated unit gets TimeoutStopSec=90 against a near-instant drain, so
systemd never kills mid-cleanup. Contract: restart the gateway, in-flight
work stops. A grace window large enough to 'save' a long agent turn would
have to outlast an unbounded task, which is impossible.

Also fixes the stale-unit warning's suggested command
(hermes gateway service install --replace -> hermes gateway install --force);
the former subcommand does not exist.

Closes NousResearch#31981
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

systemd TimeoutStopSec mismatch causes silent crash loop; suggested fix command is wrong

3 participants