summaryrefslogtreecommitdiff
path: root/internal/executor/executor_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-09 04:24:06 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-09 04:24:06 +0000
commit91595726d696ef8bdc8c3ef0a39a1d3a7be34408 (patch)
tree71d885f13d5209170a10913c69f2dde9e7703b1b /internal/executor/executor_test.go
parent02851c000399a0a9fa1cee3a6d6695de00661dab (diff)
executor: recover stale RUNNING tasks on server startup
On restart, any tasks in RUNNING state have no active goroutine. RecoverStaleRunning() marks them FAILED (retryable) and closes their open execution records with an appropriate error message. Called once from serve.go after the pool is created.
Diffstat (limited to 'internal/executor/executor_test.go')
-rw-r--r--internal/executor/executor_test.go35
1 files changed, 35 insertions, 0 deletions
diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go
index e6b8f0b..2292aa5 100644
--- a/internal/executor/executor_test.go
+++ b/internal/executor/executor_test.go
@@ -461,6 +461,41 @@ func TestPool_FailureHistoryInjectedOnRetry(t *testing.T) {
}
}
+func TestPool_RecoverStaleRunning(t *testing.T) {
+ store := testStore(t)
+ logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
+ pool := NewPool(2, map[string]Runner{"claude": &mockRunner{}}, store, logger)
+
+ // Create a task already in RUNNING state (simulating a crashed server).
+ tk := makeTask("stale-1")
+ tk.State = task.StateRunning
+ store.CreateTask(tk)
+
+ // Add an open execution record (no end time, status RUNNING).
+ store.CreateExecution(&storage.Execution{
+ ID: "exec-stale-1", TaskID: tk.ID,
+ StartTime: time.Now().Add(-5 * time.Minute),
+ Status: "RUNNING",
+ })
+
+ pool.RecoverStaleRunning()
+
+ recovered, err := store.GetTask(tk.ID)
+ if err != nil {
+ t.Fatalf("get task: %v", err)
+ }
+ if recovered.State != task.StateFailed {
+ t.Errorf("state: want FAILED, got %q", recovered.State)
+ }
+ execs, _ := store.ListExecutions(tk.ID)
+ if len(execs) == 0 || execs[0].Status != "FAILED" {
+ t.Errorf("execution status: want FAILED, got %+v", execs)
+ }
+ if execs[0].ErrorMsg == "" {
+ t.Error("expected non-empty error message on recovered execution")
+ }
+}
+
func TestPool_UnsupportedAgent(t *testing.T) {
store := testStore(t)
runners := map[string]Runner{"claude": &mockRunner{}}