[Bug] Sandboxed datetime hints can fail decoding and drop signals

Đang mở
#2,072 0 bình luận 0 reaction 1 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@VegetarianOrc đang làm issue này rồi.

Từ ngày 3/11/2026.

  • #2094 của @VegetarianOrc — đang mở

Đánh giá

Issue này chưa được đánh giá.

Mô tả

sdk-python
What are you really trying to do?

Use a datetime workflow parameter, or a dataclass containing one, with the default data converter and default sandboxed workflow runner. The README lists datetime.datetime as supported.

Describe the bug

A bare datetime hint evaluated when the sandbox reimports the workflow module can be a restricted proxy. The default converter checks hint is datetime, so it rejects that hint with:

Unserializable type during conversion: <class 'datetime.datetime'>

At d488ff59, the identity check is in temporalio/converter/_payload_converter.py:849. The default restrictions cover datetime.datetime.now, today, and utcnow (temporalio/worker/workflow_sandbox/_restrictions.py:608–613), and attribute access creates the proxy (:1033–1051). The proxy compares equal to the native class but is a different object.

Observed on that commit:

Boundary Result
Workflow run argument: bare datetime, list[datetime], or dataclass field Workflow tasks repeatedly fail with Failed decoding arguments
Signal argument Signal is recorded in history, but the worker logs Failed deserializing signal input ... dropping the signal; the handler does not run
Update argument WorkflowUpdateFailedError
Activity or child workflow result Workflow tasks fail during decoding
Query argument WorkflowQueryFailedError

The run, signal, and update tests pass when their defining module is passed through or when using UnsandboxedWorkflowRunner. A separate passed-through dataclass model also works. For passthrough, the relevant module is the user module defining the workflow or model.

Run-argument failures were also reproduced on released SDK versions 1.35.0, 1.30.0, and 1.20.0.

Not every datetime-containing hint fails. In Python 3.12.3 probes, datetime | None held the native class and decoded successfully. Optional[datetime] and Union[datetime, str] depended on typing-alias creation order: with the sandbox creating the alias first, the former failed and the latter returned a string.

Minimal Reproduction

Save as datetime_hint_repro.py and run it as a Python script.

import asyncio
import uuid
from datetime import datetime, timezone

import temporalio.testing
import temporalio.worker
import temporalio.workflow


@temporalio.workflow.defn
class Greet:
    @temporalio.workflow.run
    async def run(self, when: datetime) -> str:
        return when.isoformat()


async def main() -> None:
    async with await temporalio.testing.WorkflowEnvironment.start_local() as env:
        async with temporalio.worker.Worker(
            env.client, task_queue="tq", workflows=[Greet]
        ):
            h = await env.client.start_workflow(
                Greet.run,
                datetime(2021, 1, 2, tzinfo=timezone.utc),
                id=f"w-{uuid.uuid4()}",
                task_queue="tq",
            )
            try:
                print("RESULT:", await asyncio.wait_for(h.result(), 8))
            except asyncio.TimeoutError:
                async for ev in h.fetch_history_events():
                    if ev.HasField("workflow_task_failed_event_attributes"):
                        f = ev.workflow_task_failed_event_attributes.failure
                        while f.HasField("cause"):
                            f = f.cause
                        print("WORKFLOW TASK FAILED:", f.message)
                        break
                await h.terminate()


if __name__ == "__main__":
    asyncio.run(main())

Expected:

RESULT: 2021-01-02T00:00:00+00:00

Saved output on d488ff59:

WORKFLOW TASK FAILED: Unserializable type during conversion: <class 'datetime.datetime'>

Changing the identity check to equality produces the expected result, but that prototype has the matching and replay problems described below.

Environment/Versions
  • Linux x86-64.
  • SDK source at d488ff59; Python 3.12.3 for the minimal reproduction. Targeted regression tests also reproduced the failure on Python 3.10.22, 3.13.18, and 3.14.9.
  • Local dev server started by WorkflowEnvironment.start_local(). The original reproduction logged Temporal CLI 1.9.1 / Server 1.32.0; the replay-history recording logged CLI 1.10.0 / Server 1.32.2.
Additional context

I would like to agree on the matching and replay approach before preparing a PR.

For matching:

  • hint == datetime makes the reproductions decode, but invokes arbitrary type-hint equality. A supported dataclass with a custom metaclass could raise during comparison or compare equal and be misclassified. I would not submit that comparison unchanged.
  • A narrower approach is to recognize only the SDK's own proxy wrapping the native datetime class, using identity. RestrictionContext.unwrap_if_proxied (_restrictions.py:815–819) provides that primitive. Custom converters should retain their precedence.

For replay, I recorded four datetime-signal workflows and one string-signal control on d488ff59. All five replay on the original code. With the equality prototype, two fail: a workflow that previously took a timeout branch encounters TimerMachine ... CancelTimerCommandSent ... TimerFired, and a signal handler that starts an activity produces [TMPRL1100] Nondeterminism error: Activity machine does not handle this event. The other three replay without errors, although that does not establish unchanged handler execution. In the reverse direction, three of five prototype-recorded histories fail on the original code.

The replay choices are:

  1. Apply corrected decoding unconditionally, explicitly accepting and documenting these compatibility breaks.
  2. Guard corrected decoding with a workflow logic flag, preserving old decoding during unflagged replay and recording use of the new behavior when enabled for live execution. A staged rollout, like flag 2's recognition-before-enablement period, would address compatibility with older workers. The policy also needs to cover typed memo and last-completion-result reads, which bypass _convert_payloads (_workflow_instance.py:1400, :2063).

I favor the narrow identity check and compatibility flag. I have regression tests and saved replay histories for the original prototype; the guarded implementation still needs implementation and validation. I can prepare a PR for the agreed approach.

AI disclosure: this was prepared with AI assistance (Claude Opus 5.5 and Claude Sonnet 5.5).

Ngôn ngữ chính
Python
Star
1.2k
Fork
285
Merge trung bình
1 ngày 21 giờ
Pull request đã merge (30 ngày)
80

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của temporalio/sdk-python

Tất cả issue của temporalio/sdk-python

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.