From d75f935bf258feef473bd8a4716fe2015e99121f Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 1 Nov 2026 03:06:38 +0100 Subject: [PATCH] fix(session): answer quit before raising QuitRequested Quit raised the flag and then replied. A main loop that saw the flag in between could end the Session, and with it the connections, before the reply was given, and the agent then saw the app vanish (exit 3) instead of its quit succeed. The reply now goes first: the transport delivers a reply given before its connections close. quit --force raises the flag first as before; it exits from the reply's own thread. Co-Authored-By: Claude Opus 5.5 --- implementations/Crafter.Debug-Session.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/implementations/Crafter.Debug-Session.cpp b/implementations/Crafter.Debug-Session.cpp index 14d206d..a3d4b35 100644 --- a/implementations/Crafter.Debug-Session.cpp +++ b/implementations/Crafter.Debug-Session.cpp @@ -595,13 +595,17 @@ namespace Crafter::Debug { const auto decoded = DecodeParams(params, *exchange); if (!decoded) return; Agent(decoded->force ? "quit --force" : "quit", Value()); - quitRequested.store(true); if (decoded->force) { + quitRequested.store(true); // From the thread the reply leaves on, so the main loop // need not run: --force is for an app whose loop is stuck. return exchange->ReplyThen(MakeReply(Object({ { "quitting", Value(true) }, { "force", Value(true) } })), [] { std::_Exit(0); }); } + // The reply before the flag: a loop that sees the flag may end + // the Session at once, and the transport delivers a reply given + // before its connections close, not one given after. exchange->Reply(MakeReply(Object({ { "quitting", Value(true) } }))); + quitRequested.store(true); } // ---- the Pump thread ----