Skip to content

fix: treat empty entity operation input as no input#283

Open
YunchuWang wants to merge 1 commit into
mainfrom
yunchuwang-copilot-finds-bug-entity-op-empty-input
Open

fix: treat empty entity operation input as no input#283
YunchuWang wants to merge 1 commit into
mainfrom
yunchuwang-copilot-finds-bug-entity-op-empty-input

Conversation

@YunchuWang

Copy link
Copy Markdown
Member

Summary

Fixes a bug in the entity executor where an entity operation whose input is an empty StringValue crashes with SyntaxError: Unexpected end of JSON input and is rolled back, instead of being executed with no input.

Why this fix is needed

TaskEntityShim.executeOperation() parsed the operation input like this:

const inputValue = opRequest.getInput();
const input = inputValue ? JSON.parse(inputValue.getValue()) : undefined;

The inputValue ? guard is meant to handle the "no input" case, but it only guards against undefined. A protobuf StringValue is always a truthy object — even when it wraps an empty string. So when a sidecar/scheduler (or another language SDK) sends an operation whose input field is an empty StringValue (getValue() === ""), the guard passes and the code calls JSON.parse(""), which throws:

SyntaxError: Unexpected end of JSON input

The try/catch in executeOperation() then converts that into an operation failure and rolls back the state/actions for that operation. The caller receives a confusing SyntaxError for what should have been a successful, no-input operation.

This is also inconsistent with the rest of the codebase:

  • The orchestration and activity executors parse inputs through the shared parseJsonField() helper in utils/pb-helper.util.ts.
  • Even the same file's getSerializedState() already guards against empty strings — only the operation-input path did not.

Impact

  • Severity: Medium — entity operations incorrectly fail and roll back when they should succeed with no input.
  • Scenarios affected: operations received from a sidecar/scheduler that sends an empty StringValue for the input field; cross-SDK interop where another language SDK produces empty input wrappers; any protobuf construction that sets an empty StringValue instead of omitting the field entirely.

The fix

Replace the inline JSON.parse with the shared parseJsonField() helper:

const inputValue = opRequest.getInput();
const input = parseJsonField(inputValue);

parseJsonField():

  1. Returns undefined for a null/undefined or empty StringValue — so an empty wrapper is treated as "no input".
  2. Wraps genuine JSON parse errors with a contextual message and preserves the original error via cause (invalid JSON still fails, as before, just with a clearer message).
  3. Aligns entity input parsing with how the orchestration and activity executors already handle inputs.

Because parseJsonField() returns undefined for an empty wrapper, operation.hasInput becomes false and the entity method is dispatched with no argument — matching the intended "no input" semantics.

Tests

Added a new empty operation input suite to entity-executor.spec.ts:

  • an empty StringValue input no longer throws SyntaxError — the operation now succeeds (regression test for the reported bug);
  • the dispatched operation receives no input (hasInput === false);
  • a genuine empty-string JSON value ('""') is still delivered to the operation as "" — guarding against over-eagerly dropping legitimate empty-string input.

Validation

  • npm run build:core — compiles cleanly.
  • eslint on the changed files — clean.
  • Full core unit suite — 1052 tests pass (59 suites), including the 3 new tests.

Fixes #238

The entity executor parsed operation input with an inline
`inputValue ? JSON.parse(inputValue.getValue()) : undefined` guard. A
protobuf StringValue is always a truthy object, so an empty StringValue
wrapping "" passed the guard and reached JSON.parse(""), which throws
"SyntaxError: Unexpected end of JSON input". The operation then failed
and rolled back instead of running with no input.

Use the shared parseJsonField() helper (already used by the orchestration
and activity executors), which returns undefined for a null/empty
StringValue and wraps genuine parse errors with context via `cause`.

Adds regression tests covering empty-wrapper input, verifying the
operation is dispatched with no input, and confirming a genuine
empty-string JSON value ('""') is still delivered as "".

Fixes #238

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 21:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes entity operation input parsing in the worker’s entity executor so that an empty protobuf StringValue is treated as “no input” (rather than attempting JSON.parse("") and failing the operation/rolling back state). This aligns entity execution with the existing protobuf JSON parsing utilities already used elsewhere in the SDK.

Changes:

  • Switch entity operation input parsing from inline JSON.parse(...) to the shared parseJsonField(...) helper (treats empty wrapper as undefined and improves parse error context).
  • Add regression tests covering: empty wrapper input succeeds, dispatch receives “no input”, and explicit JSON empty-string ('""') is preserved.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/durabletask-js/src/worker/entity-executor.ts Use parseJsonField for operation input so empty StringValue doesn’t crash/rollback.
packages/durabletask-js/test/entity-executor.spec.ts Add targeted regression tests for empty-wrapper vs explicit empty-string JSON input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[copilot-finds] Bug: Entity executor uses inline JSON.parse for operation input, crashing on empty StringValue instead of treating as no input

2 participants