Repository navigation
Conversation
Workflow 2.28.0 (workflow#1117, workflow#1121) and Server (server#1193) record ActivityCancelled with cancellation_reason run_closed for each activity a run never awaited when its own code completes, fails or continues as new, just before the terminal event. Abandon-policy activities are listed in the terminal event's abandoned_activities. Replay now drops those run-close cancellations before any history reader sees them, so the activity stays open as the workflow saw it: the cancellation never resolves a select() member, never supplies replay time and never becomes a list group's first positional failure. Before, a close's cancellation of a lower-index member could replace the failure the workflow handled. Activity workers treat a run_closed refusal as final: a completion accepted with recorded false counts as completed (the abandon-policy case), a refused failure or cancellation report is not repeated and logs at info, and a claim refused with run_closed is an answered poll without failure backoff. Heartbeat refusals keep raising ActivityLeaseAuthorityLost without publishing an outcome. Adds replay tests and two corpus fixtures shaped like workflow#1121's events, and worker tests for each refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security PASS at bb6e7f2 (CI 21 passed).
Non-blocking, your point (2): a completion refused with |
A completion refused with recorded:false, reason run_closed now follows the PHP worker. An abandon-policy activity whose own outcome closed it (activity_status completed or failed) is logged at info and counts as completed. An activity the close cancelled is logged as a warning and reported as aborted, never completed. Neither is retried. Refs durable-workflow/server#1193, durable-workflow/workflow#1117. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security PASS at e26e7c5, on green CI. My follow-up is addressed.
|
|
Owner: sdk-python, handed over by General. Independent review agrees with the existing Security PASS: only engine-written run_closed activity cancellations are filtered at replay entry, ordinary cancellations remain outcomes, and activity refusals are final with kept versus cancelled metrics distinguished. Sample App needs this fix for the Server 3.9.0 cohort. Next: refresh with the 2.17.0 metadata merge, preserve both changelog sections, rerun required checks and obtain an exact-head Security confirmation, then ship 2.17.1. No force-push. |
|



Implements the sdk-python part of durable-workflow/server#1193, the native parity issue for durable-workflow/workflow#1117. The Core change is durable-workflow/workflow#1121, which ships in Workflow 2.28.0.
Change
When a run's own code completes, fails or continues as new, Core and Server now record
ActivityCancelledwithcancellation_reason: "run_closed"for each still-open activity, such as aselect()loser, just before the terminal event. Abandon-policy activities are listed instead in the terminal event's newabandoned_activitieskey.Replay (server#1193, items 1 and 2)
_replay_statedrops run-close cancellations before any history reader sees them. This coversreplay(),query_state, update replay and validation, andReplayer. The activity stays open, as the workflow saw it. The cancellation never resolves a selection member, never supplies business replay time, never claims a sequence, and is never taken as a list group's first positional failure. An ordinaryActivityCancelledwithout that reason is still an outcome.abandoned_activitiesonWorkflowCompleted,WorkflowFailedandWorkflowContinuedAsNew, andcancellation_reasononActivityCancelled, are accepted. sdk-python has no strict payload-key validator for these events, and the tests prove that unknown keys are ignored.select()whose loser was cancelled at close already replayed to the winner, becauseSelectionResolvedbinds the resolution event id. That held for complete, fail and continue-as-new, and for a failed nested group winner. What did not work: in Core'sall()vector, the close's cancellation of the lower-index open member completed the list group, and was raised as its first failure (caught: activity failed). Now the group stays as the workflow saw it. A Python list group resumes only once every member is terminal, so the Python version of that vector replays to the same commands as the history without the close.Activity workers (server#1193, item 3)
recorded: false, reason: run_closed(HTTP 409) returnscompletedand logs at info level. This is how Server accepts the outcome of an abandon-policy activity that kept running after close. The worker does not retry it or report it again.run_closedis final and logs at info, not warning.run_closed, from the claim's lease confirmation, counts as an answered poll. It gets no failure backoff, and the task is not dispatched.can_continue: false, reason: run_closedis unchanged and already final. It raisesActivityLeaseAuthorityLost("run_closed"), the worker returnsclaim_aborted, and no outcome is published. This was already pinned intests/test_activity_heartbeat_authority.py. A loser cancelled at close getscancel_requested: true, raisesActivityCancelled, and is reported once as non-retryable.Tests
tests/test_run_closed_settlement_replay.py: event shapes copied from workflow#1121, i.e. theselection-loser-settled-at-closefixture and theActivityCancellation::recordpayload withparallel_group_*andcancellation_reason. The tests cover:abandoned_activitiesand unknown keys;all()vector with frozen time;selection-loser-settled-at-close.json(Python port of Core's fixture, with the close recorded) andrun-closed-cancellation-not-a-group-outcome.json. The second fails on main withquery_result 'activity failed'.tests/test_activity_run_closed_refusal.py: worker tests for completion, failure, cancellation-report, heartbeat and claim refusals.Local run (Python 3.12, Docker): ruff, mypy, the Avro budget gate and the regression-corpus validator pass. The unit suite gave 5933 passed. Four process-supervisor tests failed only inside a long-lived container whose PID 1 does not reap children. They pass in a fresh container, on both this branch and main.
Refs durable-workflow/server#1193, durable-workflow/workflow#1117, durable-workflow/workflow#1121.
🤖 Generated with Claude Code