fix(gateway): don't restart supervised services on clean exit

The s6 finish script for profile-gateway services restarted on ANY exit
except EX_CONFIG (78) — including clean exit 0. Restart-on-normal-exit
turns an intentional stop into a reconnect loop: the ashriel-discord
storm in #76435 made 1,000+ connections and got the bot token reset by
Discord.

The finish script now exits 125 (permanent failure, no restart) for both
clean exit 0 and EX_CONFIG; only non-zero, non-78 exits (genuine crashes)
restart normally.

Scope note: #76435 bundles a second, separable symptom (Windows desktop
updater showing the literal 'managed outside dashboard' sentinel). Its
root cause is undiagnosed and #22733 covers the dialog-explanation path;
this PR is the gateway half only.

Tests: behavioral — the rendered finish script is executed via sh for
exit codes 0/78/1/137, asserting no-restart for clean stop and fatal
config, restart for crashes. Pre-existing EX_CONFIG test still passes.
This commit is contained in:
spfcraze
2026-08-01 22:08:11 -04:00
committed by Teknium
parent ebb2859132
commit 5f619cfa0e
2 changed files with 32 additions and 2 deletions
+10 -2
View File
@@ -716,8 +716,12 @@ class S6ServiceManager:
When the gateway exits with EX_CONFIG (78) — a fatal
configuration error such as a token collision or no messaging
platforms — we tell s6-supervise to stop restarting by exiting
125 (permanent failure). Any other exit code lets s6 restart
normally. See #51228.
125 (permanent failure). A clean exit 0 is an intentional stop,
not a crash: restarting after it turns any normal gateway exit
into a reconnect loop (the ashriel-discord storm in #76435 —
1,000+ connections and a provider token reset). Only non-zero,
non-78 exits (genuine crashes) let s6 restart normally.
See #51228, #76435.
"""
from gateway.restart import GATEWAY_FATAL_CONFIG_EXIT_CODE
@@ -727,9 +731,13 @@ class S6ServiceManager:
"# shellcheck shell=sh\n"
"# $1 = exit code from the run script.\n"
f"# Exit {code} (EX_CONFIG) = fatal config error — don't restart.\n"
"# Exit 0 (clean stop) = intentional stop — don't restart.\n"
f'if [ "$1" = "{code}" ]; then\n'
" exit 125\n"
"fi\n"
'if [ "$1" = "0" ]; then\n'
" exit 125\n"
"fi\n"
"exit 0\n"
)
+22
View File
@@ -286,6 +286,28 @@ def test_render_finish_script_exits_125_on_ex_config() -> None:
assert "exit 0" in text
def test_render_finish_script_does_not_restart_on_clean_exit(tmp_path) -> None:
"""Behavioral: the rendered finish script, executed for each run-exit
code, must exit 125 (no restart) for clean exit 0 and EX_CONFIG 78,
and exit 0 (restart) for genuine crashes (#76435 — restart-on-normal-
exit turned a supervised gateway into a reconnect storm)."""
import subprocess
script = tmp_path / "finish"
script.write_text(S6ServiceManager._render_finish_script())
script.chmod(0o755)
def finish_exit(run_exit_code: int) -> int:
proc = subprocess.run(["sh", str(script), str(run_exit_code)],
capture_output=True)
return proc.returncode
assert finish_exit(0) == 125 # clean stop — no restart
assert finish_exit(78) == 125 # fatal config — no restart
assert finish_exit(1) == 0 # crash — s6 restarts
assert finish_exit(137) == 0 # SIGKILL crash — s6 restarts