diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-11 08:25:23 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-11 08:25:23 +0000 |
| commit | 889791e9a2d9585a35e72402bfc9e6feb49fbb88 (patch) | |
| tree | 1f9c4ecdb71906e81c4511ca1b3c1ae428581a44 /internal/storage | |
| parent | 1b5e7177769c79f9e836a55f9c008a295e2ff975 (diff) | |
fix: clear question_json when restarting a task via ResetTaskForRetry
A BLOCKED task that fails on resume would keep its stale question_json
after being restarted. The frontend then showed "waiting for your input"
with the old prompt even though the task was running fresh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/storage')
| -rw-r--r-- | internal/storage/db.go | 2 | ||||
| -rw-r--r-- | internal/storage/db_test.go | 35 |
2 files changed, 36 insertions, 1 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go index 5ba0786..aaf1e09 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -237,7 +237,7 @@ func (s *DB) ResetTaskForRetry(id string) (*task.Task, error) { configJSON, _ := json.Marshal(t.Agent) now := time.Now().UTC() - if _, err := tx.Exec(`UPDATE tasks SET state = ?, config_json = ?, updated_at = ? WHERE id = ?`, + if _, err := tx.Exec(`UPDATE tasks SET state = ?, config_json = ?, question_json = NULL, updated_at = ? WHERE id = ?`, string(task.StateQueued), string(configJSON), now, id); err != nil { return nil, err } diff --git a/internal/storage/db_test.go b/internal/storage/db_test.go index d28a4a8..2956be0 100644 --- a/internal/storage/db_test.go +++ b/internal/storage/db_test.go @@ -628,6 +628,41 @@ func TestDeleteTask_DeepSubtaskCascadeAtomic(t *testing.T) { } } +// TestResetTaskForRetry_ClearsQuestionJSON verifies that restarting a BLOCKED +// or FAILED task via ResetTaskForRetry clears any stale question so the frontend +// does not show a stale "waiting for input" prompt. +func TestResetTaskForRetry_ClearsQuestionJSON(t *testing.T) { + db := testDB(t) + now := time.Now().UTC() + tk := makeTestTask("retry-task", now) + tk.State = task.StatePending + db.CreateTask(tk) + + // Transition to BLOCKED with a question. + db.UpdateTaskState("retry-task", task.StateQueued) + db.UpdateTaskState("retry-task", task.StateRunning) + db.UpdateTaskState("retry-task", task.StateBlocked) + db.UpdateTaskQuestion("retry-task", `{"question":"which branch?"}`) + + // Simulate the task failing and being restarted. + db.UpdateTaskState("retry-task", task.StateFailed) + + if _, err := db.ResetTaskForRetry("retry-task"); err != nil { + t.Fatalf("ResetTaskForRetry: %v", err) + } + + got, err := db.GetTask("retry-task") + if err != nil { + t.Fatalf("GetTask: %v", err) + } + if got.QuestionJSON != "" { + t.Errorf("question_json should be cleared after reset, got %q", got.QuestionJSON) + } + if got.State != task.StateQueued { + t.Errorf("state should be QUEUED, got %q", got.State) + } +} + func TestStorage_GetLatestExecution(t *testing.T) { db := testDB(t) now := time.Now().UTC() |
