[Bug] Sandboxed datetime hints can fail decoding and drop signals
Los mantenedores suelen responder en 1 día
Evaluación
Este issue todavía no se ha evaluado.
Descripción
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 == datetimemakes 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:
- Apply corrected decoding unconditionally, explicitly accepting and documenting these compatibility breaks.
- 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
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de temporalio/sdk-python
-
Dificultad 4/5 3-5 días Aptitud para principiantes 58/100
temporalio/sdk-python#2092 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 18/100
temporalio/sdk-python#2080 ·
Los mantenedores suelen responder en 1 día
-
[Bug] TaskGroup cancellation of mixed Temporal operations can break replayPosiblemente ocupada @VegetarianOrc la tomó hace 2 días. Abiertosdk-python
temporalio/sdk-python#2069 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
sdk-python
Dificultad 4/5 3-5 días Aptitud para principiantes 22/100
temporalio/sdk-python#2067 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
[Bug] lambda_worker otel.apply_defaults leaks a span exporter thread and gRPC channel on every warm invocationPosiblemente ocupada @avanish-garg la tomó hace 7 días. Abiertobug sdk-python
Dificultad 4/5 1-2 días Aptitud para principiantes 30/100
temporalio/sdk-python#2040 ·
Los mantenedores suelen responder en 1 día
Todos los issues de temporalio/sdk-python
Issues similares
-
[MOVIE]: Airport '77 (1977)Abiertorequest-theme
Dificultad 1/5 Menos de una hora Aptitud para principiantes 78/100
LizardByte/ThemerrDB#9883 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
Project-MONAI/MONAI#9258 ·
Los mantenedores suelen responder en 3 días
-
catalog:deprecation
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
surendranb/free-inference#31 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
fritzprix/libr-agent#2612 ·
Los mantenedores suelen responder en 1 día
-
needs-triage presubmit-gate
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
gke-labs/kube-agents#5001 ·
Los mantenedores suelen responder en 1 día