From 6d4f6de67616b2ff67ead4f8062d2c38b97968ac Mon Sep 17 00:00:00 2001 From: dtoro Date: Sat, 11 Jul 2026 20:25:21 +0200 Subject: [PATCH] fix(agent): mark a task failed when its resume permanently gives up (B3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix B3 of plans/2026-07-11-nomos-agent-code-review.md. resumeSession's retry loop (used by both auto-continuation and panel-answered questions) already retried once on a transient LLM failure, but if BOTH attempts came back empty/erroring, the code just logged and returned — the task was left at whatever status it already had (typically 'executing' or 'awaiting_input') with no outcome, no operator-visible signal beyond an inert error line buried in the transcript, and no way to tell a genuinely stuck task apart from one quietly still working. On permanent failure, now calls store.completeTask(outcome='failure', a summary built from the error) so the task board reflects reality instead of showing a task that looks perpetually in-progress. Uses context.Background() for that write, matching resumeSession's own persistence pattern, since the context that led to the failure may itself be in a bad state. This doesn't prevent the operator from continuing to work the task via a fresh chat message afterward — it only replaces silent hanging with a real status. A full live induction of a permanent LLM outage would require breaking the model/API-key config for the whole nomos container — too invasive for this fix's priority. Verified instead that the new branch stays correctly dormant on the happy path: ran a real ask_operator → panel-answer → resume cycle end-to-end and confirmed the task landed at status='executing' with no outcome set, proving the failure-handling code doesn't false-positive on a normal successful resume. Co-Authored-By: Claude Opus 4.8 --- cmd/nomos/continue.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmd/nomos/continue.go b/cmd/nomos/continue.go index 072cf74..ac532b7 100644 --- a/cmd/nomos/continue.go +++ b/cmd/nomos/continue.go @@ -176,6 +176,21 @@ func (a *agent) resumeSession(ctx context.Context, sessionID, note string) { if errText != "" && finalText == "" { slog.Error("nomos: resume produced no response after retry", "session", sessionID, "error", errText) + // Give the task a real, operator-visible terminal state instead of + // leaving it silently stuck at whatever status it was in (typically + // 'executing' or 'awaiting_input') forever. Before this, a + // permanently-failed resume was invisible beyond a log line — the + // task board just showed a task that never changed, with nothing + // telling the operator it needed attention. Marking it failed here + // doesn't prevent the operator from continuing to work the task via + // a fresh chat message afterward; it just stops the silent hang. + summary := fmt.Sprintf("Auto-resume failed after retrying: %s", errText) + if len(summary) > 200 { + summary = summary[:200] + "…" + } + if cerr := a.store.completeTask(context.Background(), sessionID, "failure", summary); cerr != nil { + slog.Error("nomos: failed to mark task failed after resume gave up", "session", sessionID, "error", cerr) + } } persist() // final state — same row, updated one last time with the concluding text }