{"record":{"id":"a42000571ccaee10","repo":"langchain-ai/deepagents","slug":"server-offload-operations-may-not-write-sorted-fo","errorCode":null,"errorMessage":"Server offload operations may not write {sorted(forbidden)} to the checkpoint.","messagePattern":"Server offload operations may not write (.+?) to the checkpoint\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/offload_api.py","lineNumber":993,"sourceCode":"        prepared = prepare_operation_cost(state, thread_id)\n        update: dict[str, Any] = {**execution.update, **prepared.update}\n        if forbidden := set(update) - _WRITABLE_STATE_CHANNELS:\n            # A security boundary, not a defensive assertion: this route commits\n            # to the latest checkpoint rather than the one it read, so a\n            # `messages` write here would be unattributed to any run and could\n            # clobber messages a concurrent run appended in that window. See\n            # THREAT_MODEL.md (TB10/DF27) before relaxing this.\n            #\n            # Checked as an allowlist against `OffloadStateUpdate` rather than\n            # for `messages` alone, so the runtime guard enforces the same\n            # invariant the type states instead of a subset of it: a future\n            # merge that adds any other channel is refused here too.\n            msg = (\n                \"Server offload operations may not write \"\n                f\"{sorted(forbidden)} to the checkpoint.\"\n            )\n            prepared.rollback()\n            raise RuntimeError(msg)\n        if not update:\n            # Nothing to persist, but `prepare_operation_cost` already drained\n            # the recorder. Returning without rolling back would delete that\n            # spend from the thread's lifetime total (the drain is destructive).\n            prepared.rollback()\n            return {\"status\": \"complete\", \"result\": execution.result}\n        commit = asyncio.create_task(\n            _commit_deferred_archive(\n                client,\n                thread_id,\n                checkpoint_id,\n                execution,\n                update,\n                prepared,\n            )\n        )\n        cancellation = await _join_task_deferring_cancellation(commit)\n        commit.result()","sourceCodeStart":975,"sourceCodeEnd":1011,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/offload_api.py#L975-L1011","documentation":"The server-side offload handler validates that the merged state update only touches allowlisted checkpoint channels (`_WRITABLE_STATE_CHANNELS`). If the offload execution result or the cost-preparation update contributes any other channel (notably `messages`), it rolls back the drained cost record and raises this RuntimeError. This is a deliberate security boundary: because the route commits to the latest checkpoint rather than the one it read, an unattributed `messages` write could clobber messages a concurrent run appended.","triggerScenarios":"Calling POST offload (via `offload`) where `OffloadOperation.execute` returns an update dict containing channels outside the allowlist — e.g. a custom OffloadOperation writing `messages`, or a modified/merged execution returning extra state keys.","commonSituations":"Custom offload/compaction operations that write summarized `messages` server-side (moved to client-side event-based compaction); SDK upgrades that add channels to `OffloadStateUpdate` without updating `_WRITABLE_STATE_CHANNELS`; test doubles of the operation that return full state.","solutions":["Change the offload operation to return only allowlisted channels (e.g. `_summarization_event`, token counters) instead of `messages` or other channels.","If a new channel is legitimately required, extend `_WRITABLE_STATE_CHANNELS` after reviewing THREAT_MODEL.md (TB10/DF27).","If caused by an SDK upgrade, pin the previous `deepagents` version and update the offload middleware to the new summarization-event contract."],"exampleFix":"// before\nclass MyOffload(OffloadOperation):\n    def execute(self, state, runtime):\n        return {\"messages\": summarized, ...}\n// after\nclass MyOffload(OffloadOperation):\n    def execute(self, state, runtime):\n        # emit an event, not a messages write\n        return {\"_summarization_event\": {\"cutoff_index\": idx, \"summary_message\": summary}}","handlingStrategy":"validation","validationCode":"FORBIDDEN = set(execution.update) - _WRITABLE_STATE_CHANNELS\nassert not FORBIDDEN, f\"offload update writes {FORBIDDEN} — restrict to allowlisted channels\"","typeGuard":"def is_allowlisted(update: dict[str, Any]) -> bool:\n    return set(update) <= _WRITABLE_STATE_CHANNELS","tryCatchPattern":"try:\n    result = await offload_endpoint(request)\nexcept RuntimeError as e:\n    if \"may not write\" in str(e):\n        fix_operation_channels()  # inspect operation.update keys\n    raise","preventionTips":["Type custom operations against OffloadStateUpdate so only allowlisted channels compile.","Add a unit test asserting the operation's update keys are a subset of _WRITABLE_STATE_CHANNELS.","Re-read THREAT_MODEL.md (TB10/DF27) before touching the allowlist."],"tags":["security","checkpoint","offload","state-validation"],"backgroundTag":"forbidden-state-write","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}