Repository navigation
Show how Python activity errors become terminal and reach workflow code #343
Description
Delivered
Delivered in PR #370, merge 7a820a79d8de20e82e511ecf20bfe3bba6f33883.
Exact-head qualification, artifact deployment and actual Pages publication passed. All eight live Python guides return HTTP 200 with the new terminal-business-errors section, correct canonical/alternate links, matching executable example and working table-of-contents navigation. English, Japanese and Simplified Chinese were inspected at 390px in light/dark themes with CJK fonts; no document overflow.
The published-package fixture receipt preserves all seven original executions: short/qualified matches, subclass/cause counterexamples, wrapped business error, and the exact guide's rejected/accepted inputs. Twelve activities, fourteen attempts, zero repairs; no failed workflow was reclassified.
See the published Python guide. This explains the existing SDK behavior rather than proposing a runtime classification change.
Original report and acceptance
Problem
The Python guide shows how to raise NonRetryableError and list non_retryable_error_types, but leaves out what users need for business errors:
- Only the outermost exception decides retries. The worker reports its type and class, and the engine compares exact names. Subclasses and causes don't match. So
raise RuntimeError(...) from NonRetryableError(...)is retried. NonRetryableError(message, cause=...)keeps the original cause, but the guide never shows wrapping with it.- The guide never shows workflow code catching
ActivityFailedto handle a terminal business error and continue.
Behavior is correct; the docs are missing.
Acceptance criteria
- The Python guide states that the outermost exception's short or qualified class name must match
non_retryable_error_typesexactly, and that subclasses and causes don't count. - It shows wrapping a caught exception with
NonRetryableError(..., cause=e). - It shows a workflow catching
ActivityFailedfor a business error and continuing.
Activity
rmcdaniel commentedon Oct 23, 2026
Product review: the requested business-error flow already has SDK and runtime primitives (
NonRetryableError, exact-name retry policy andActivityFailed). The missing piece is a correct example and explicit classification rules. I am taking this documentation work, with installed-package qualification first so a product defect is not hidden by prose.Next: check exact short/qualified names, subclass and chained-cause controls, and wrapping a cause with
NonRetryableErroragainst the published Python SDK. Verify that workflow code catches the terminal business failure and performs its next durable activity. Then update the Python guide and all seven translations with matching examples and source hashes.rmcdaniel commentedon Oct 23, 2026
Published-package qualification passes for the requested business-error rules and the exact guide example.
Tuple: normally installed Python SDK 2.9.0 (wheel SHA-256
1e7547e895485dc8f670bd1b9cc34184b135f249db8da98b16b250bfc6002f73), published PHP Server 2.11.0 atsha256:b29157eb2cf844916debbbb3c01b3257672e0dece778a9c6fa539043a3038f02, Python 3.12 / amd64 / SQLite. No SDK/Server source overlay.ActivityFailed, completes another durable activity, then completes normallyRuntimeErrorcaused byNonRetryableErrorNonRetryableError(..., cause=error){"order_id":"order-123","status":"rejected"}{"order_id":"order-123","status":"accepted"}All seven fresh originals complete within their original 30-second run/execution bounds. The five classification controls each execute a real subsequent
website343.recordactivity, proving the workflow continues after the terminal activity failure. A local assertion also verifies the wrapper retains the original Python__cause__; this is not a cross-SDK cause-object guarantee.After the client stopped and the API exited 0 / no OOM, a SELECT-only audit confirms 12 activity executions, 14 activity attempts, zero repairs, exactly one
WorkflowCompletedand noWorkflowFailedper original. It verifies the attempt counts above against actual execution/attempt rows, not fixture expectations alone. The database hash is unchanged by the audit. The API container and network are removed; raw DB retirement follows this public receipt.Draft implementation is #370. The source examples and prose are translated into all seven other languages; matching code blocks and source hashes pass locally. Full documentation checks and deployment remain next. No missing SDK/runtime feature or new package floor was inferred.
Original outcomes and reproducible fixture sources
The isolated Server is bootstrapped with its own SQLite data and alias
apiin a task-only Docker network. Use the normal registry Python package and runtimeout 120 python -u /task/probe.py; then extract the Python block from #370's terminal-business-errors section toexample.pyand runtimeout 60 python -u /task/check_example.py. Both use normal Worker registration/dispatch and Avro payloads. Mount a task directory for the receipts. Stop all writers before the SELECT-only audit. Authentication is disabled only within this isolated fixture; no host port or production namespace is used.outcomes.json
[ { "case": "short", "workflow_id": "website343-short-daebb96f", "run_id": "01m5kxehcjhtwbhjf93y1fyddf", "output": { "status": "rejected", "continued": { "recorded": "short" }, "failure": { "exception_type": "BusinessRuleError", "exception_class": "__main__.BusinessRuleError", "non_retryable": true, "activity_type": "website343.validate" } }, "expected_validation_attempts": 1 }, { "case": "qualified", "workflow_id": "website343-qualified-d1af1eb7", "run_id": "01m5kxeraexert99mxd2587aaf", "output": { "status": "rejected", "continued": { "recorded": "qualified" }, "failure": { "exception_type": "BusinessRuleError", "exception_class": "__main__.BusinessRuleError", "non_retryable": true, "activity_type": "website343.validate" } }, "expected_validation_attempts": 1 }, { "case": "subclass", "workflow_id": "website343-subclass-79d8784d", "run_id": "01m5kxey8670a186a075qa0kh5", "output": { "status": "rejected", "continued": { "recorded": "subclass" }, "failure": { "exception_type": "SpecificBusinessRuleError", "exception_class": "__main__.SpecificBusinessRuleError", "non_retryable": false, "activity_type": "website343.validate" } }, "expected_validation_attempts": 2 }, { "case": "cause", "workflow_id": "website343-cause-fd44fcfa", "run_id": "01m5kxf5bqnqp60e1d88ckjxvp", "output": { "status": "rejected", "continued": { "recorded": "cause" }, "failure": { "exception_type": "RuntimeError", "exception_class": "builtins.RuntimeError", "non_retryable": false, "activity_type": "website343.validate" } }, "expected_validation_attempts": 2 }, { "case": "wrapped", "workflow_id": "website343-wrapped-f72aed7f", "run_id": "01m5kxfch5zydj961y7heq706a", "output": { "status": "rejected", "continued": { "recorded": "wrapped" }, "failure": { "exception_type": "NonRetryableError", "exception_class": "durable_workflow.errors.NonRetryableError", "non_retryable": true, "activity_type": "website343.validate" } }, "expected_validation_attempts": 1 } ]example-outcomes.json
[ { "amount": 0, "workflow_id": "website343-example-0-dc6895ab", "run_id": "01m5kxk5g5k3b1rbv6h82fmgj1", "output": { "order_id": "order-123", "status": "rejected" } }, { "amount": 7, "workflow_id": "website343-example-7-891717d7", "run_id": "01m5kxk8gyxrcwkzc1c4tzby75", "output": { "order_id": "order-123", "status": "accepted" } } ]probe.py (SHA-256
d03dcc053b9acca95e77da86803ab47613cee3ae813a90679f5a50a2bad3ce58)example.py (SHA-256
d98a3ea81af4edd10cbb3fa78e4edbf9bc5193a571106385c4cc5932c3946a80)check_example.py (SHA-256
6ca971cd31d6597ccc60812db9e0452dee4b6e5675828b46a9966eb2debe0a6c)audit.py (SHA-256
5f6c7972057d61742e2d327d5accd326af848e7ecc04ff98f1f6e456133efaf7)rmcdaniel commentedon Oct 23, 2026
Delivered in PR #370, merge
7a820a79d8de20e82e511ecf20bfe3bba6f33883.Exact-head qualification, artifact deployment and actual Pages publication passed. All eight live Python guides return HTTP 200 with the new
terminal-business-errorssection, correct canonical/alternate links, matching executable example and working table-of-contents navigation. English, Japanese and Simplified Chinese were inspected at 390px in light/dark themes with CJK fonts; no document overflow.The published-package fixture receipt preserves all seven original executions: short/qualified matches, subclass/cause counterexamples, wrapped business error, and the exact guide's rejected/accepted inputs. Twelve activities, fourteen attempts, zero repairs; no failed workflow was reclassified.
See the published Python guide. This explains the existing SDK behavior rather than proposing a runtime classification change.