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

Abierto
#2,072 0 comentarios 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@VegetarianOrc ya está trabajando en esto.

Desde el 3/11/2026.

  • #2094 de @VegetarianOrc — abierto

Evaluación

Este issue todavía no se ha evaluado.

Descripción

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).

Lenguaje dominante
Python
Estrellas
1.2k
Forks
285
Merge medio
1 d 21 h
PR fusionados (30 d)
80

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de temporalio/sdk-python

Todos los issues de temporalio/sdk-python

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.