Skip to content

[copilot-finds] Bug: Orchestration executor missing EntityUnlockSent handler causes duplicate unlock actions on replay #235

@github-actions

Description

@github-actions

Problem

The orchestration executor's processEvent switch statement in orchestration-executor.ts handles all entity-related history event types except ENTITYUNLOCKSENT. When an orchestration acquires entity locks and releases them, the sidecar records EntityUnlockSent events in the history. On replay, these events fall through to the default case and are silently logged as unknown events.

This means the unlock actions created by exitCriticalSection() during replay are never cleaned up from _pendingActions. Since setComplete() intentionally preserves pending actions (for fire-and-forget actions like sendEvent), the stale unlock actions are included in the returned action list and re-sent to the sidecar on every replay.

Affected files:

  • packages/durabletask-js/src/worker/orchestration-executor.ts (lines 131–194, switch statement)

Root Cause

All other entity event types have dedicated handlers that call validateEntityAction() to match the event against the pending action and remove it from _pendingActions:

  • ENTITYOPERATIONCALLEDhandleEntityOperationCalled
  • ENTITYOPERATIONSIGNALEDhandleEntityOperationSignaled
  • ENTITYLOCKREQUESTEDhandleEntityLockRequested
  • ENTITYOPERATIONCOMPLETEDhandleEntityOperationCompleted
  • ENTITYOPERATIONFAILEDhandleEntityOperationFailed
  • ENTITYLOCKGRANTEDhandleEntityLockGranted
  • ENTITYUNLOCKSENT → ❌ missing

The exitCriticalSection() method creates one SendEntityMessageAction (unlock type) per locked entity. On replay, the corresponding EntityUnlockSent history events are not processed, so these actions accumulate.

Proposed Fix

Add a handleEntityUnlockSent method and case in the switch statement, following the same validateEntityAction pattern used by the other entity event handlers.

Impact

  • Severity: Medium — Duplicate unlock messages sent to the sidecar on every orchestration replay involving entity locks. The sidecar may handle these gracefully, but it creates unnecessary network traffic and violates the replay invariant that stale actions should not be re-emitted.
  • Scenarios affected: Any orchestration that uses lockEntities() / critical sections with entity locking. The bug manifests on every subsequent replay after the initial execution that releases locks.

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