Visitar URL original
[Bug] ECS observer leaves runs PENDING when capacity provider can't place task by goingforstudying-ctrl · Pull Request #22417 · PrefectHQ/prefect · GitHub
Skip to content

[Bug] ECS observer leaves runs PENDING when capacity provider can't place task - #22417

Merged
desertaxle merged 7 commits into
PrefectHQ:mainfrom
goingforstudying-ctrl:fix/ecs-task-failed-to-start
Jul 1, 2026
Merged

desertaxle merged 7 commits into
PrefectHQ:mainfrom
goingforstudying-ctrl:fix/ecs-task-failed-to-start

Conversation

@goingforstudying-ctrl

Copy link
Copy Markdown
Contributor

Noticed this while debugging a stuck pipeline on an ECS work pool — when a capacity provider can't get an instance (GPU shortage, ASG at ceiling, etc.), ECS leaves the task in PROVISIONING for ~30 minutes and then stops it with stopCode: TaskFailedToStart and an empty containers array.

The observer's mark_runs_as_crashed handler runs on STOPPED events, but only proposes Crashed when a container exited non-zero. With an empty containers array, containers_with_non_zero_exit_codes is empty, any() returns False, and the crash proposal is skipped. The only thing the handler does is log a diagnostic — but the flow run just sits in PENDING forever.

The fix

Added a check for stopCode == "TaskFailedToStart" with empty containers, and propose Crashed with the stoppedReason in the message so operators can see WHY it failed (e.g. TaskFailedToStart: RESOURCE:GPU).

The diagnostic (infra-level logging via diagnose_ecs_task) already handled this case — the gap was only in the state transition.

Files changed

  • src/integrations/prefect-aws/prefect_aws/observers/ecs.py — the core fix
  • src/integrations/prefect-aws/tests/observers/test_ecs_observer_crash.py — unit tests for the TaskFailedToStart detection logic

Closes #22410

When a capacity provider cannot place an ECS task, the task
transitions to STOPPED with stopCode TaskFailedToStart and an
empty containers array. Previously, mark_runs_as_crashed only
proposed Crashed when a container exited non-zero, so runs
whose tasks never placed remained PENDING forever.

Now the handler checks for TaskFailedToStart with empty
containers and proposes Crashed with the stop reason included
in the crash message. The diagnostic (infra level) was already
handled by diagnose_ecs_task; this fix closes the state
transition gap.

Closes PrefectHQ#22410
@github-actions github-actions Bot added bug Something isn't working labels Jul 1, 2026
- Apply ruff formatting to ecs.py and test_ecs_observer_crash.py
- Remove unused imports (uuid, mark_runs_as_crashed, StateType, Crashed)
- Remove unused variable assignment in test helper

@desertaxle desertaxle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @goingforstudying-ctrl! The changes look good, but I want to make sure the your changes are covered with tests before merging.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new tests never appear to call mark_runs_as_crashed, so I don't think your code changes are covered by tests. Could you refactor these tests to invoke the actual handler and make assertions about the Crashed proposal?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. I refactored the tests to call mark_runs_as_crashed directly with mocked prefect.get_client and propose_state, matching the pattern used in the existing TestMarkRunsAsCrashed class. The new tests verify that TaskFailedToStart produces a Crashed proposal and that edge cases (normal exit, wrong stopCode, final flow run, missing flow run) are handled correctly.

goingforstudying-ctrl and others added 5 commits July 1, 2026 10:23
…ed proposal

Replace inline detection logic with real handler calls using the same
mock pattern as the rest of TestMarkRunsAsCrashed.  New tests verify
that TaskFailedToStart proposes Crashed, normal exits do not, empty
containers without the right stopCode do not, and edge cases like final
flow runs and missing flow runs are handled correctly.
Remove extra blank line between third-party and first-party imports
to satisfy ruff's isort rules.
…_args

The test test_task_failed_to_start_crash_message_includes_reason was
checking for a 'message' key in propose_state's kwargs, but the actual
call passes the message inside a Crashed state object via state=Crashed(message=...).

Fix extracts the state from kwargs first, then reads .message from it.

@desertaxle desertaxle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks @goingforstudying-ctrl!

@desertaxle
desertaxle merged commit ab6a0dc into PrefectHQ:main Jul 1, 2026
14 checks passed
br413 pushed a commit to br413/prefect that referenced this pull request Jul 14, 2026
…lace task (PrefectHQ#22417)

Co-authored-by: goingforstudying-ctrl <goingforstudying-ctrl@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ECS observer: a task ECS abandons during provisioning is never marked Crashed, leaving a hanging flow run

2 participants