summaryrefslogtreecommitdiff
path: root/internal/executor/claude.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor/claude.go')
-rw-r--r--internal/executor/claude.go12
1 files changed, 12 insertions, 0 deletions
diff --git a/internal/executor/claude.go b/internal/executor/claude.go
index 9184333..db4d0fa 100644
--- a/internal/executor/claude.go
+++ b/internal/executor/claude.go
@@ -285,6 +285,18 @@ func (r *ClaudeRunner) execOnce(ctx context.Context, args []string, workingDir s
stdoutW.Close()
// killDone is closed when cmd.Wait() returns, stopping the pgid-kill goroutine.
+ //
+ // Safety: this goroutine cannot block indefinitely. The select has two arms:
+ // • ctx.Done() — fires if the caller cancels (e.g. timeout, user cancel).
+ // The goroutine sends SIGKILL and exits immediately.
+ // • killDone — closed by close(killDone) below, immediately after cmd.Wait()
+ // returns. This fires when the process exits for any reason (natural exit,
+ // SIGKILL from the ctx arm, or any other signal). The goroutine exits without
+ // doing anything.
+ //
+ // Therefore: for a task that completes normally with a long-lived (non-cancelled)
+ // context, the killDone arm fires and the goroutine exits. There is no path where
+ // this goroutine outlives execOnce().
killDone := make(chan struct{})
go func() {
select {