From 244feb4d7ec3058eeacb526f74431aa079689c3b Mon Sep 17 00:00:00 2001 From: "Claude Opus 4.6" Date: Mon, 18 May 2026 21:31:47 +0000 Subject: [PATCH] application: add graceful shutdown config for Bandit Configures Bandit with 615s drain timeout and 620s OTP shutdown window so in-flight Claude CLI calls (up to 600s) complete before systemd kills the process on restart. Co-Authored-By: Claude Sonnet 4.6 --- lib/symbiont/application.ex | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/symbiont/application.ex b/lib/symbiont/application.ex index 77369a8..d25a346 100644 --- a/lib/symbiont/application.ex +++ b/lib/symbiont/application.ex @@ -12,9 +12,26 @@ defmodule Symbiont.Application do └── Bandit (Symbiont.API) — HTTP API In test mode, the supervisor starts empty — tests manage their own processes. + + ## Graceful shutdown + + Bandit is configured with a long ThousandIsland `shutdown_timeout` so that + in-flight `/task` requests (which can take up to 600s while Claude runs) + are not killed when systemd restarts the service. The Bandit child spec + also carries a matching `:shutdown` value so the OTP supervisor itself + waits long enough before forcing a kill. + + The matching systemd unit must set `TimeoutStopSec=620` for this to take + effect end to end. """ use Application + # 615s for Bandit to drain its sockets, 620s for the OTP child shutdown + # window. Keep these slightly larger than the Claude CLI timeout (600s) + # used by Muse's SMTP dispatcher. + @bandit_drain_ms 615_000 + @bandit_child_shutdown_ms 620_000 + @impl true def start(_type, _args) do if Application.get_env(:symbiont, :port) == 0 do @@ -32,13 +49,23 @@ defmodule Symbiont.Application do port = Application.get_env(:symbiont, :port, 8111) engram_db = Application.get_env(:symbiont, :engram_db, "/data/symbiont/engram.db") + bandit_spec = + Supervisor.child_spec( + {Bandit, + plug: Symbiont.API, + port: port, + scheme: :http, + thousand_island_options: [shutdown_timeout: @bandit_drain_ms]}, + shutdown: @bandit_child_shutdown_ms + ) + children = [ {Task.Supervisor, name: Symbiont.TaskSupervisor}, {Symbiont.Ledger, data_dir: data_dir}, {Symbiont.Queue, data_dir: data_dir}, {Symbiont.Engram, db_path: engram_db}, {Symbiont.Heartbeat, []}, - {Bandit, plug: Symbiont.API, port: port, scheme: :http} + bandit_spec ] opts = [strategy: :rest_for_one, name: Symbiont.Supervisor]