Skip to content

Defer pipeline failure propagation until the task result is saved - #34

Draft
cw-jlu wants to merge 1 commit into
taskiq-python:masterfrom
cw-jlu:fix/defer-pipeline-failure-until-post-save
Draft

cw-jlu wants to merge 1 commit into
taskiq-python:masterfrom
cw-jlu:fix/defer-pipeline-failure-until-post-save

Conversation

@cw-jlu

@cw-jlu cw-jlu commented Nov 12, 2026

Copy link
Copy Markdown

A failed attempt can currently publish the pipeline's final error while that task is still retrying. A caller waiting for the pipeline then receives the first failure even though the retry and subsequent steps eventually succeed.

Propagate terminal pipeline errors from post_save rather than on_error. Taskiq finishes all retry middleware decisions before saving results and skips both saving and post-save hooks for NoResultError attempts. This keeps an in-flight retry from publishing a premature final result, independently of the relative order of PipelineMiddleware and SimpleRetryMiddleware. Ordinary failures and exhausted retries still fail the final pipeline task; a failing final step keeps its own saved result. Successful continuation uses the existing path.

Fixes #26 (the original premature-result report). The separate comment about delayed SmartRetry scheduling changing _pipe_data serialization is not addressed by this patch.

Assignment was requested at #26 (comment); no actual assignment has been made. The open group-support PR does not modify this middleware, and no overlapping retry PR was found.

Validation actually run

  • Windows / Python 3.14.7, repository lock: Taskiq 0.12.1, Pydantic 2.12.5. pytest -q -n 2: 11 passed, the complete repository suite.
  • The final eight new regressions copied to an untouched d5d4da6e1733e2886ef955b505f7818690316587 worktree: 3 failed, 5 passed. The two successful-retry orders expose an already-ready final error while the retry waits on an Event; the third failure shows that an explicitly suppressed NoResultError also incorrectly marks the pipeline failed. Five controls cover exhausted retries/ordinary terminal errors in both orders, plus the final-step saved result.
  • Regression tests execute Taskiq's actual InMemoryBroker receiver/middleware/result path, without replacing those components. Events observe completion of all error hooks and hold the retry deterministically; no timing sleep is used to manufacture the failure.
  • Separate integration check using real local Redis 5.0.14.1 (RESP2), taskiq-redis 1.1.2, redis-py 6.4.0, and Taskiq 0.12.6: both middleware orders wrote a premature error on the baseline; neither did on the patch. After releasing the retry, all four baseline/patch cases persisted the eventual success value 4 with exactly two attempts. This uses a real Redis result backend with an in-process InMemoryBroker executor; it is not a separate worker-process or RedisStreamBroker end-to-end test.
  • Whole-repository Black/Ruff/mypy and all repository pre-commit hooks passed; git diff --cached --check passed.

Not run locally: the full Python/OS/Pydantic CI matrix, delayed SmartRetry/scheduler serialization, a separate worker process, or result-backend outage/failover. no_result_on_retry=False deliberately permits attempt results to be saved and is outside the default suppressed-result behavior tested here.

Review status

AI-assisted investigation, implementation, and testing. Please keep this PR in Draft pending independent and account-owner review; no completed human review is claimed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Retrying task produces the error as the pipeline result

1 participant