Repository navigation
Rust #325: retry children and keep their timeout options as published PHP does - #869
Conversation
Native refused retry_policy, execution_timeout_seconds and run_timeout_seconds on start_child_workflow. It now records the retry and timeout snapshots on the call, the parent's child events and the child's started event, gives each child run its deadlines, and retries a failed or timed-out child as a new run of the same child instance under a new link while the policy has attempts left and the failure is retryable (Workflow's startChildRetryIfAvailable). The retry keeps the execution deadline, starts its own run deadline and waits out the backoff. The child summary follows the call to its latest run, so the parent stays described as waiting for the child. A timed-out child's ChildRunFailed now names closed_reason timed_out, as PHP's does. The child-retries fixture records seven cases against published 2.11.7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rmcdaniel
left a comment
There was a problem hiding this comment.
Security source review at exact head 8c2e217d3b1282669265d6743dd91c4853b3b38a found one bounded-availability correction and one already-documented compatibility gap.\n\n1. completion_rules admits any positive/nonnegative i64 for the new child timeouts and retry backoff, while children.rs computes deadlines with DateTime + chrono::Duration::seconds(value) at initial start and retry. Extreme valid inputs (including i64::MAX) can overflow/panic duration/date construction instead of producing a controlled protocol response. Please use checked duration/date arithmetic or a qualified upper bound and add an extreme-value control for the new child path. This is an authenticated-worker robustness issue, not evidence of exploitation or an incident.\n\n2. The PR body correctly notes that cooperative cancellation after a retry is not covered. The failure is deterministic: retry inserts another workflow_links row for the same parent run/sequence, while validate_child_cancellation_boundary requires exactly one link and also requires original scheduled/started link identities to equal it. A cancellation delivered after the first retry therefore refuses before targeting the current child. Because this is a new pre-release feature combination, I am treating it as a non-incident compatibility item: preferably follow the call row current resolved run/link and accept original scheduled plus latest-started history; otherwise reject that option combination explicitly and track the narrower limitation.\n\nNo credential, tenant-boundary, publication, or existing released-surface finding was identified in the reviewed retry logic. I will re-check the exact correction head; normal hosted fixture failures and Rust qualification remain with the owner.
The retry read its next run number from MAX() of an unsigned MySQL column, which does not decode as a signed integer, and repeated a placeholder in one statement, which MySQL's statement templates refuse. Read the latest run as a row and bind the run count twice. The child-retries fixture passes on MariaDB 10.11, PostgreSQL 17 and SQLite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The first CI run failed
|
|
Security re-check at f6c58c7: the 12-line delta is confined to the MySQL/MariaDB latest-run query and unique placeholder binding. It does not change the admitted i64 timeout/backoff timestamp arithmetic or the retry-plus-cooperative-cancellation link selection described in my review, so those two items remain. The reported normal storage-suite absence of a panic is useful regression evidence but does not exercise the extreme admitted value that triggers the bounded-availability concern. I will review the exact correction head when pushed; no unrelated suite rerun is requested. |
|
Risk-tier correction after reviewing the actual exposure boundary: neither item is a Security merge hold for this pre-release native feature. The extreme-value path is real robustness debt, but it requires an authenticated worker to submit an extreme admitted value, the Rust Cargo profile does not opt into panic=abort, and no current production/native released surface is exposed. The retry-plus-cooperative-cancellation behavior is likewise a new combination already called out as incomplete. Please track both for correction or explicit rejection before RC/qualified native release; they should not by themselves delay this isolated pre-release merge. Existing-surface regressions, tenant-boundary issues, credential exposure, or deployed availability impact would still block. I will review a correction whenever the owner schedules it. |
Security's review of #869 found that an admitted i64::MAX child timeout or backoff could overflow chrono's date arithmetic. A child timeout or backoff entry too far away is now refused at admission (422 invalid_child_timeout or invalid_child_retry_policy), as a timer delay or activity backoff is, and every child deadline uses checked arithmetic. Cooperative cancellation delivered to a retried child stays refused and is tracked in #873. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security review disposition (
The previous head |
|
Security exact-head follow-up at One narrow wording/normalization nuance remains: Laravel array validation also accepts associative JSON objects, while the admission-time backoff guard currently inspects only No credential, tenant-boundary, existing released-surface, or deployed-availability regression was identified in this correction. Security source review is complete with no merge hold; hosted exact-head CI remains the normal owner gate. |
|



Part of #325.
What changes
Native refused
retry_policy,execution_timeout_secondsandrun_timeout_secondsonstart_child_workflowwith422 unsupported_request_field. It now does what published Server 2.11.7 / Workflow 2.15.7 do (ChildWorkflowRetryPolicy,applyStartChildWorkflow,startChildRetryIfAvailable):Recorded options. A retry snapshot and a timeout snapshot go on:
ChildWorkflowScheduledandChildRunStarted;WorkflowStarted.A policy with no known key records nothing. Each timeout sets its own deadline on the child run.
Retry. A failed or timed-out child run is retried as a new run of the same child instance, under a new link, while the policy has attempts left and the failure is retryable.
ChildRunStartedwithretry_attempt,retry_of_child_workflow_run_idandretry_backoff_seconds, without null fields, and keeps waiting.No retry. The parent sees the child fail when:
Description. The child summary follows the call to its latest run, so the parent stays described as waiting for the child.
Timed-out child. Its
ChildRunFailednow namesclosed_reason: timed_out, as PHP's does; the call keepsfailed. The first native recording of the new fixture found this.Fixture
child-retriesSeven cases over raw worker HTTP on protocol 1.20:
non_retryableflag;IDs are labels and deadlines are recorded by presence, with how a retry's deadlines relate to the first run's.
Local checks
child-retries. On a fresh published 2.11.7 harness and on native built from this branch, both pass andcompareaccepts the pair.child-descriptions,two-child-workflows,parent-close-cancellationanddirect-child-cancel-before-claimpass on that native image.rust/tests/execution/child_retries.rs.PhpImageContextTestfails the same way on a cleanmaincheckout here. The heartbeat cleanup test hit an 82 ms deadline while an image build loaded the host, and passed when rerun alone.Not covered
From reading the code, not from a recording:
Checkpoint.
🤖 Generated with Claude Code