Repository navigation
Go-SDK: Coordinator-mode task failures never transition to UP_FOR_RETRY #67797
Description
Background
In coordinator mode the Go SDK reports every task failure as a terminal TaskState(failed) frame and then exits 0, so a TaskInstance with retries configured is recorded FAILED and never retried. The retry decision is made client-side in the SDK; the Go runtime does not implement it yet.
task-sdk/src/airflow/sdk/execution_time/task_runner.py is the source of truth for SDK behavior. _handle_current_task_failed (~L1640):
if ti._ti_context_from_server and ti._ti_context_from_server.should_retry:
return RetryTask(...), UP_FOR_RETRY
return TaskState(state=FAILED, ...), FAILEDThe supervisor confirms this is load-bearing — supervisor.py final_state (~L1658): on exit 0 the runtime's frame is taken verbatim, so a TaskState(failed) frame becomes terminal FAILED. UP_FOR_RETRY is reachable only via a distinct RetryTask message, or a non-zero exit combined with should_retry (supervisor.py:1359, :1668).
Current Go state:
go-sdk/pkg/execution/task_runner.go— task error, panic, and bad-UUID paths all returnTaskStateMsg{State: TaskStateFailed}; there is no retry branch.go-sdk/pkg/execution/messages.go—decodeTIRunContextdoes not parseshould_retry/max_tries, and there is noRetryTaskoutbound message type. So neither the input nor the message for the retry path exists.
should_retry already travels on the wire as a field of TIRunContext (StartupDetails.ti_context); see the supervisor schema.json and _generated.py (TIRunContext.should_retry).
What needs to happen
- Parse
should_retry(andmax_tries) fromStartupDetails.ti_contextindecodeTIRunContext. - Add a
RetryTaskoutbound message type mirroring the Python wire shape. - In
task_runner.go, on task failure emitRetryTask(→UP_FOR_RETRY) whenshould_retryis true, otherwise keepTaskState(failed). - Add tests covering success / fail / retry.
Acceptance criteria
- A failing coordinator-mode task whose TI has retries remaining is recorded
UP_FOR_RETRY, notFAILED. should_retryfromti_contextis parsed and honored.- The TODO at the failure site in
go-sdk/pkg/execution/task_runner.gois removed.
Context
- Found during local review of Go-SDK: Implement coordinator-mode runtime entry point and task runner #67318 (coordinator-mode runtime entry point and task runner).
- Source of truth:
task-sdk/src/airflow/sdk/execution_time/task_runner.pyandsupervisor.py. - Out of scope (separate follow-ups): custom
RetryPolicy/RetryAction(_apply_retry_policy_or_default) and the other non-success terminals (DeferTask,RescheduleTask,SkipDownstreamTasks). - Sibling Java SDK gap: AIP-108: Java SDK coordinator-mode task failures never transition to UP_FOR_RETRY #67798.
Drafted-by: Claude Code (Opus 4.7); reviewed by @jason810496 before posting
Activity
[-]Coordinator-mode task failures never transition to UP_FOR_RETRY[/-][+]Go-SDK: Coordinator-mode task failures never transition to UP_FOR_RETRY[/+]onlyarnav commentedon Jun 2, 2026
Hey @jason810496 ! Just a heads-up that I'm taking a look at this also because it is similar to #67798
My approach: Similar to what we did in the Java SDK, the Go SDK currently treats every failure as a terminal failed state because it doesn't process the retry context. I am updating messages.go to properly decode should_retry and max_tries from the ti_context, and adding a new RetryTaskMsg type. From there, I'll update executeTask in task_runner.go to emit RetryTaskMsg instead of TaskStateMsg(failed) if a task panics or returns an error while should_retry is true.
Question: I noticed the old TODO in task_runner.go specifically called out that we lose retries on exit 0. Just want to confirm—aside from emitting RetryTask, we don't need to change the process exit code itself for the supervisor to handle this correctly, right?
4 remaining items