Skip to content

[copilot-finds] Bug: Entity StateShim.setState() corrupts cache on serialization failure #145

@github-actions

Description

@github-actions

Problem

In packages/durabletask-js/src/worker/entity-executor.ts, the StateShim.setState() method (line 89–101) sets cachedValue before calling JSON.stringify(). When serialization fails (e.g., circular reference), the internal cache is left in an inconsistent state:

  • cachedValue points to the invalid (non-serializable) object
  • serializedValue retains the previous valid serialized state
  • If cacheValid was true from a prior getState() or setState(), the next getState() returns the invalid cached object instead of re-parsing the last valid serialized state

Root Cause

The assignment this.cachedValue = state on line 90 executes unconditionally before the try block that calls JSON.stringify(state). When JSON.stringify throws, the error is re-thrown but the cache is already corrupted. The cacheValid flag is not reset in the catch path, so if it was previously true, getState() skips re-parsing and returns the corrupted cachedValue.

Additionally, the error wrapping drops the original error cause — the { cause: e } option is not used.

Proposed Fix

  1. Move this.cachedValue = state inside the try block, after JSON.stringify() succeeds
  2. In the catch block, invalidate the cache (cacheValid = false, cachedValue = undefined) so getState() falls back to re-parsing the last valid serializedValue
  3. Add { cause: e } to the error wrapper to preserve the original error chain

Impact

Severity: Medium — affects entity state consistency when entity code catches setState errors and attempts to recover by reading state.

Affected scenarios:

  • ITaskEntity implementations that directly use operation.state.setState() / operation.state.getState() and handle serialization errors gracefully
  • Any entity operation that catches a setState error and continues using state (reads corrupted cache instead of the last valid state)

Mitigating factor: The outer executeOperation() try/catch calls rollback() on any unhandled error, which resets the cache. The bug only manifests when entity code catches the setState error internally.

Metadata

Metadata

Assignees

No one assigned

    Labels

    copilot-findsFindings from daily automated code review agent

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions