fix(kanban): clear failure/crash diagnostics while a retry is in flight

A retried task (→ running) kept showing "crashed Nx": the in-flight run
has no outcome yet, so the trailing crash scan skipped it and kept
counting the prior streak, and the consecutive_failures counter lingers.
Exempt `running` from both repeated_failures and repeated_crashes so a
fresh attempt clears the banner until it itself resolves (re-fires if the
new run also fails).
This commit is contained in:
Brooklyn Nicholson 2026-07-09 15:59:50 -05:00
parent e87c495dc2
commit aea570db4e
2 changed files with 24 additions and 2 deletions

View file

@ -535,8 +535,14 @@ def _rule_repeated_failures(task, events, runs, now, cfg) -> list[Diagnostic]:
to retry, so a lingering failure streak is history, not a signal.
(``complete_task`` resets the counter, but a manual done e.g. a
dashboard drag ends no run and used to leave the flag stuck.)
A fresh attempt in flight (``running``) is also exempt: retrying a
task should clear the stale failure banner until this attempt also
resolves. Otherwise a card that's actively trying again still shows
"failed Nx", which reads as a current failure. It re-fires if the new
run fails too (status leaves ``running`` with a recorded outcome).
"""
if _task_field(task, "status") in ("done", "archived"):
if _task_field(task, "status") in ("done", "archived", "running"):
return []
threshold = _positive_int(cfg.get(
"failure_threshold",
@ -662,8 +668,13 @@ def _rule_repeated_crashes(task, events, runs, now, cfg) -> list[Diagnostic]:
history, and a manual done (dashboard drag) appends no ``completed``
run to break the crash streak, so the flag was permanent (#kanban
desktop dogfood). Done means done.
``running`` is exempt too: a fresh attempt is in flight, and its
in-flight run (no outcome yet) doesn't break the trailing crash scan,
so a retried card kept showing "crashed Nx" over an active run. The
banner re-fires if the new attempt also crashes.
"""
if _task_field(task, "status") in ("done", "archived"):
if _task_field(task, "status") in ("done", "archived", "running"):
return []
failure_threshold = int(cfg.get(
"failure_threshold",

View file

@ -282,6 +282,17 @@ def test_failure_rules_exempt_terminal_statuses():
assert kd.compute_task_diagnostics(task, [], runs) == []
def test_failure_rules_exempt_running_retry():
# Retrying a task (→ running) puts a fresh attempt in flight; its
# in-flight run (no outcome) doesn't break the trailing crash scan,
# so the past streak used to keep flagging over an active retry.
# A running card must clear the failure/crash banner until this
# attempt itself resolves.
runs = [_run(outcome="crashed", run_id=1), _run(outcome="crashed", run_id=2)]
task = _task(status="running", assignee="crashy", consecutive_failures=3)
assert kd.compute_task_diagnostics(task, [], runs) == []
def test_stuck_in_blocked_fires_past_threshold():
now = int(time.time())
task = _task(status="blocked")