Problem
In internal/a2a/handler.go, continueTask (line 236-237) creates a new cancel context and directly overwrites h.cancels[taskID] without first calling the old cancel function. Meanwhile, the original execute() goroutine continues running after RequestInput and always reaches cleanupCancel(t.ID) at line 301.
This creates a race condition: if the original execute() goroutine reaches cleanupCancel after continueTask has stored the new cancel, it cancels the new context, causing the resumed task to fail or behave unexpectedly.
File and Lines
internal/a2a/handler.go lines 235-237 (continueTask overwrites cancel without calling old)
internal/a2a/handler.go line 301 (execute calls cleanupCancel unconditionally)
Trigger Scenario
Time G1 (original execute) G2 (continueTask) cancels[taskID]
---- ------------------------------ ------------------------ ---------------
T1 RequestInput called [oldCancel]
(task → InputRequired)
T2 ...execution wrapping up...
T3 h.cancels[taskID] = cancel [newCancel]
T4 cleanupCancel(t.ID) called
- c = cancels[taskID] = newCancel
- c() — CANCELS THE NEW CONTEXT!
- delete(cancels[taskID]) []
T5 safego.Go(execute) starts
with already-canceled context
- Task reaches input-required state via
RequestInput()
- Original
execute() goroutine continues past the work phase to line 301
- Client sends follow-up message →
continueTask() creates new cancel at line 236-237
- Original
execute() reaches cleanupCancel(t.ID) at line 301
cleanupCancel reads the map, finds the new cancel, calls it, and deletes it
- The resumed task starts with an already-canceled context
Expected vs Actual Behavior
Expected: When resuming a task via continueTask, the new context should remain valid until the task completes or is explicitly canceled.
Actual: The old execute() goroutine's cleanupCancel races with continueTask and can cancel the new context, causing the resumed task to immediately fail or exhibit timeout/cancellation errors.
Severity
High — The resumed task silently receives a canceled context. This is difficult to reproduce and debug because it depends on precise timing between the original execution winding down and the client sending a follow-up.
Fix Suggestion
In continueTask, cancel the old context before creating the new one:
// Cancel the old context first (original execute may still be running)
if oldCancel, ok := h.cancels[taskID]; ok {
oldCancel()
}
taskCtx, cancel := context.WithTimeout(context.Background(), h.timeout)
h.cancels[taskID] = cancel
Alternatively, cleanupCancel should only cancel if the task is in a terminal state, not input-required.
Problem
In
internal/a2a/handler.go,continueTask(line 236-237) creates a new cancel context and directly overwritesh.cancels[taskID]without first calling the old cancel function. Meanwhile, the originalexecute()goroutine continues running afterRequestInputand always reachescleanupCancel(t.ID)at line 301.This creates a race condition: if the original
execute()goroutine reachescleanupCancelaftercontinueTaskhas stored the new cancel, it cancels the new context, causing the resumed task to fail or behave unexpectedly.File and Lines
internal/a2a/handler.golines 235-237 (continueTask overwrites cancel without calling old)internal/a2a/handler.goline 301 (execute calls cleanupCancel unconditionally)Trigger Scenario
RequestInput()execute()goroutine continues past the work phase to line 301continueTask()creates new cancel at line 236-237execute()reachescleanupCancel(t.ID)at line 301cleanupCancelreads the map, finds the new cancel, calls it, and deletes itExpected vs Actual Behavior
Expected: When resuming a task via
continueTask, the new context should remain valid until the task completes or is explicitly canceled.Actual: The old
execute()goroutine'scleanupCancelraces withcontinueTaskand can cancel the new context, causing the resumed task to immediately fail or exhibit timeout/cancellation errors.Severity
High — The resumed task silently receives a canceled context. This is difficult to reproduce and debug because it depends on precise timing between the original execution winding down and the client sending a follow-up.
Fix Suggestion
In
continueTask, cancel the old context before creating the new one:Alternatively,
cleanupCancelshould only cancel if the task is in a terminal state, not input-required.