Yufeng He 8eb3be59bd
fix(sandbox): unwrap Overwrite-wrapped state in ensure_sandbox_initialized (#4429)
* fix(sandbox): unwrap Overwrite-wrapped state in ensure_sandbox_initialized

The same fork-restored wrapper that crashed after_agent also reaches the sandbox init path, where sandbox_state.get() on the Overwrite object raises AttributeError. Share the unwrap helper from #4381's follow-up module deerflow/sandbox/overwrite.py and apply it at both init sites.

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* fix(sandbox): note why discarding fork_restored at the reuse sites is safe

* fix(sandbox): unify the Overwrite unwrap helper and pin the fall-through

- middleware.py now imports unwrap_sandbox from overwrite.py instead of
  keeping a second local copy whose docstring had already drifted; the
  shared helper covers both crash forms (subscript TypeError and the
  .get()-form AttributeError)
- test the acquire fall-through: when the fork-restored id is gone from
  the provider, a fresh sandbox is acquired and the stale wrapped state
  is replaced by the plain acquired dict
- the reuse-path test now also asserts runtime.state["sandbox"] stays
  wrapped, pinning the don't-treat-as-owned contract after_agent relies on

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* fix(sandbox): unwrap Overwrite state in the sibling sandbox readers

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

---------

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-29 07:04:26 +08:00

22 lines
897 B
Python

"""Helpers for ``langgraph.types.Overwrite``-wrapped channel values."""
from langgraph.types import Overwrite
def unwrap_sandbox(sandbox: object) -> tuple[object, bool]:
"""Unwrap an ``Overwrite``-wrapped sandbox channel value, if present.
Fork-restored checkpoints can deliver the sandbox channel still wrapped in
``langgraph.types.Overwrite`` (the rollback restore applies replace-style
writes through a state-mutation graph in delta checkpoint mode). Reading
``sandbox["sandbox_id"]`` or ``sandbox.get("sandbox_id")`` on the wrapper
itself crashes, so unwrap before use.
Returns ``(value, fork_restored)``. The wrapped form replays the parent
thread's sandbox state, so callers must not treat the sandbox as owned by
this run (e.g. release it).
"""
if isinstance(sandbox, Overwrite):
return sandbox.value, True
return sandbox, False