pub async fn serve(opts: Opts) -> Result<()>Expand description
Serve the UI until Ctrl-C, finishing a run the loop has in flight.
The server itself owns no state, so nothing here is graceful for the HTTP side’s sake: the connections go with the dropped listener, which costs a phone one change-stream reconnection it was going to make anyway.
The signal branch is not optional now that the loop lives in this process.
daemon::serve_until listens for Ctrl-C itself, and a registered
handler is what stops the signal terminating the process - so without a
branch of our own, the first Ctrl-C after the operator started the loop
would stop the loop and leave magi web listening forever, unkillable
from the terminal it was started in.
What it waits for is the loop, not the sockets. A run in flight is
finished first, for the reason daemon::serve gives: killing the graph
mid-node leaves worktrees, branches and agent sessions behind and throws
away every agent call already paid for.
Bind the port, waiting briefly for a predecessor to let go of it.
A restart hands the address from one process to the next, and the old one
holds its listener until it unwinds. A single bind can lose that race,
and for a restart triggered from a phone that means the deck never comes
back with no terminal around to say why.
Bounded, and only for the one error a wait can fix: anything else fails at once, because retrying it would turn a clear message into a silence.