diff --git a/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java b/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java index 2541660..2ebf041 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java +++ b/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java @@ -510,6 +510,12 @@ public final class FleetApp { ctx.status(400).json(Map.of("error", "unknown_profile", "detail", e.getMessage())); } catch (PeerUnreachableException e) { ctx.status(502).json(Map.of("error", "spawn_timeout", "detail", e.getMessage())); + } catch (HerdrException e) { + // fleetd #304: not every herdr failure on the spawn path is a readiness timeout, so + // PeerUnreachableException above does not cover this. Without this catch the exception + // escapes to Javalin's default 500, while fleet_spawn reports the same failure as a + // clean named error (FleetMcp.spawn) — the #297 one-door-guarded shape. + herdrError(ctx, e); } } @@ -530,13 +536,29 @@ public final class FleetApp { return (s == null || s.isBlank()) ? null : s; } - /** Tear a worker down by pane id. */ + /** + * Tear a worker down by pane id. + * + *
fleetd #304: the {@code HerdrException} catch is not cosmetic. {@code release} deregisters
+ * the session, notifies the release listener and preserves a dirty worktree before it
+ * calls {@code launcher.stop}, so a throw from that stop arrives after the teardown the caller
+ * asked for has already happened. Letting it escape gave Javalin's default 500, which tells the
+ * caller to retry — and the retry finds nothing in the registry, reaches the same stop, and
+ * throws again, so it can never succeed. {@code herdrError} instead answers 404 ("the pane is
+ * gone, stop retrying") or 502 ("herdr is upstream and broken, a retry may help"), matching what
+ * {@code fleet_stop} reports for the same failure.
+ */
private void stopMember(Context ctx) {
String paneId = ctx.pathParam("paneId");
if (!allow(ctx, routeAction("DELETE /members/{paneId}"), paneId)) {
return;
}
- sessions.release(paneId);
+ try {
+ sessions.release(paneId);
+ } catch (HerdrException e) {
+ herdrError(ctx, e);
+ return;
+ }
ctx.status(204);
}
diff --git a/fleetd/src/test/java/dev/ltms/fleet/rest/FleetAppTest.java b/fleetd/src/test/java/dev/ltms/fleet/rest/FleetAppTest.java
index a3e7a67..be273a1 100644
--- a/fleetd/src/test/java/dev/ltms/fleet/rest/FleetAppTest.java
+++ b/fleetd/src/test/java/dev/ltms/fleet/rest/FleetAppTest.java
@@ -415,7 +415,11 @@ class FleetAppTest {
FakeHerdr herdr = new FakeHerdr().agentNameTakenTimes(99);
int port = start(herdr, "http://gx00.gw:8000", Set.of("gx00.gw"));
- assertEquals(500, req(port, "POST", "/members").statusCode());
+ // fleetd #304: 502, not Javalin's default 500 — the herdr failure is named, and the body
+ // carries herdr's own message, matching what fleet_spawn reports for the same failure.
+ HttpResponse