Skip to content

Native worker poll responses omit the protocol_version body field #1001

Closed

Description

@rmcdaniel

Defect

Native Server 3.0.5 returns POST /api/worker/workflow-tasks/poll without the protocol_version body field. PHP Server 2.11.8 includes it. The Server reference says worker-plane responses "always echo the server's advertised version in the same header plus the protocol_version body field".

The published CLI checks both, so dw workflow-task:poll fails against native 3.0.5:

{"error":"Server error: invalid worker-protocol response body for [/worker/workflow-tasks/poll]; expected protocol_version same-major compatible with [1.0].","exit_code":1,"command":"workflow-task:poll","namespace":"default"}

Evidence (2026-11-03, lab machine, loopback only)

Same request against both published images: a pending workflow on a fresh task queue, a registered worker, then a poll with X-Durable-Workflow-Protocol-Version: 1.0.

Server Image Header Body protocol_version Body keys
PHP 2.11.8 durableworkflow/server@sha256:3848d5d5cd1b23bd2881e3fdecea64cc44b72e6fb812e577c2db6d0872d065dd 1.20 "1.20" poll_status, protocol_version, server_capabilities, task
Native 3.0.5 durableworkflow/server@sha256:3cb6e5bf8ec766ac2abd933369b40a77af62794b6d7b921ce21d3fb263e88b72 1.20 missing poll_status, server_capabilities, task

CLI: published dw-linux-x86_64 2.3.3, checksum-verified against the release SHA256SUMS.

I checked only the poll response with a leased task. Other worker-plane responses may have the same gap.

Acceptance

  • Native worker-plane responses include protocol_version in the body, matching the header, as PHP does.
  • A native regression test pins the body field on poll (task and empty poll) and on the other worker-plane responses that PHP covers.
  • dw workflow-task:poll 2.3.3 succeeds against the fixed native image.

Found while reproducing durable-workflow/cli#59. The Rust CLI (durable-workflow/cli#72) will keep the same body check.

Activity

  1. rmcdaniel commented on Nov 3, 2026

    @rmcdaniel
    MemberAuthor

    Triage (Server). This is a confirmed defect against the documented contract.

    Contract

    • docs/server-reference.md:911-914: "worker-plane responses always echo the server's advertised version in the same header plus the protocol_version body field."
    • PHP enforces this centrally. app/Support/WorkerProtocol.php:760-770, WorkerProtocol::json, does $payload['protocol_version'] ??= $version; $payload['server_capabilities'] ??= … on every worker response, so no endpoint can omit it.

    Native cause (3.0.5, bc24589d)

    • The worker middleware (rust/src/runtime/http.rs:691-697) adds only the X-Durable-Workflow-Protocol-Version header to every worker-plane response. Each handler adds the body field itself.
    • Error paths (admission_error, worker_response, the guard refusals) do add it. The successful workflow-task poll responses don't:
      • rust/src/runtime/store.rs:2003, {"task", "poll_status": "leased", "server_capabilities"};
      • :2041, the empty poll.
    • Other worker-plane success bodies may have the same gap. That's why a per-endpoint patch is the wrong shape.

    Proposed fix

    • Mirror PHP's central rule in the worker middleware. For a worker-plane JSON object response, set protocol_version and server_capabilities only when absent (??= semantics), so existing endpoint values are untouched.
    • Read the body with a bound. Non-JSON and non-object bodies pass through unchanged.
    • Tests:
      • a route sweep over every worker-plane endpoint's success, empty and refusal shapes, asserting that body and header agree;
      • a regression for poll with a task and empty poll;
      • a counterfactual against 3.0.5's middleware.
    • Installed check: dw workflow-task:poll 2.3.3 against the fixed image.

    Impact

    • The published SDKs aren't affected. Installed acceptance of PHP, Python and Rust against native passed, and they don't require the body field.
    • What breaks is the published CLI's raw worker-protocol commands, such as dw workflow-task:poll 2.3.3, which check both the header and the body.

    Release: recommendation and decision are with General in chat. The fix is small. 3.1.0 is the natural home, because main now carries 3.1.0 work (#978). A 3.0.6 would need a release branch from bc24589d.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:defectA public product behavior is incorrect

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions