Skip to content

[Bug]: SUBSCRIBE_TO_TASK_BINDING uses POST, but the vendored spec declares GET for /tasks/{id}:subscribe #201

Description

@DontFretBrett

What happened?

SUBSCRIBE_TO_TASK_BINDING sends SubscribeToTask over HTTP+JSON as POST, but the A2A specification vendored in this repository declares that method as GET. As a result, the four STREAM-SUB-* MUST requirements cannot pass against an implementation that follows the specification.

The TCK's own spec copyspecification/a2a.proto, vendored from a2aproject/A2A commit 173695755607e884aa9acf8ce4feed90e32727a1 (branch v1.0.0), per specification/version.json:

rpc SubscribeToTask(...) {
  option (google.api.http) = {
    get: "/tasks/{id=*}:subscribe"
  };
}

The bindingtck/requirements/base.py:147:

SUBSCRIBE_TO_TASK_BINDING = TransportBinding(
    grpc_rpc=OperationType.SUBSCRIBE_TO_TASK.value,
    jsonrpc_method=OperationType.SUBSCRIBE_TO_TASK.value,
    http_json_method=HTTP_POST,          # ← spec says GET
    http_json_path=PATH_TASK_SUBSCRIBE,
)

The path constant is correct (PATH_TASK_SUBSCRIBE = "/tasks/{id}:subscribe", base.py:69); only the method differs.

Why this looks like an oversight rather than a decision

I compared every HTTP+JSON binding in tck/requirements/base.py against the annotations in specification/a2a.proto. Ten of eleven match exactly. SubscribeToTask is the only mismatch:

Operation TCK binding Vendored spec
SendMessage POST /message:send post:
SendStreamingMessage POST /message:stream post:
GetTask GET /tasks/{id} get:
ListTasks GET /tasks get:
CancelTask POST /tasks/{id}:cancel post:
SubscribeToTask POST /tasks/{id}:subscribe get:
CreatePushNotificationConfig POST /tasks/{id}/pushNotificationConfigs post:
GetPushNotificationConfig GET …/{configId} get:
ListPushNotificationConfigs GET …/pushNotificationConfigs get:
DeletePushNotificationConfig DELETE …/{configId} delete:
GetExtendedAgentCard GET /extendedAgentCard get:

Notably SendStreamingMessage is POST because the spec says post: — so "streaming implies POST" isn't the rule being applied; the spec is followed everywhere except here.

Impact

Four MUST-level requirements are unpassable for a spec-conformant SUT:

  • STREAM-SUB-001 — SubscribeToTask returns Task as first event
  • STREAM-SUB-002 — SubscribeToTask stream terminates at terminal state
  • STREAM-SUB-003 — SubscribeToTask rejects terminal tasks
  • STREAM-SUB-004 — SubscribeToTask returns TaskNotFoundError for invalid task

Our server implements GET /tasks/{id}:subscribe per the spec and correctly returns 405 Method Not Allowed for POST. The TCK reports this as a MUST failure. Because these are MUST-level, a release gate keyed on MUST results is held shut by a correct implementation — and the obvious "fix" on our side would be to accept POST, i.e. to become non-conformant to the specification in order to pass the conformance suite.

Versions affected

  • 1.0.0.alpha2 (29063fe95e903cddac5d8ff811ab94df1ad6ef86) — the tag we pin
  • main (5996b79f9cefa6fc390980e383e358a66fb9e49e at time of writing) — still HTTP_POST, so upgrading does not resolve it

Suggested fix

 SUBSCRIBE_TO_TASK_BINDING = TransportBinding(
     grpc_rpc=OperationType.SUBSCRIBE_TO_TASK.value,
     jsonrpc_method=OperationType.SUBSCRIBE_TO_TASK.value,
-    http_json_method=HTTP_POST,
+    http_json_method=HTTP_GET,
     http_json_path=PATH_TASK_SUBSCRIBE,
 )

tck/transport/http_json_client.py:296 also documents the POST behaviour in its docstring ("""Subscribe to task updates via ``POST /tasks/{id}:subscribe``.""") and would want updating alongside. I have not checked whether _request_streaming needs changes to issue a bodyless GET — that's the one part of this I can't assert.

Scope of what I verified

  • I checked only the HTTP+JSON bindings. I did not evaluate the gRPC or JSON-RPC mappings, so I can't say whether they're affected.
  • This appears related to but distinct from [Bug]: Normalize A2A method mapping source across JSON-RPC transport and validators #138 ("Normalize A2A method mapping source across JSON-RPC transport and validators"), which concerns JSON-RPC method names. This is an HTTP verb in the HTTP+JSON binding. If you'd prefer to fold it into [Bug]: Normalize A2A method mapping source across JSON-RPC transport and validators #138 as one "mapping sources disagree with the spec" theme, that seems reasonable — they share a root cause in mappings being maintained separately from the spec they encode.
  • A possible systemic follow-up: since the spec is vendored in-repo with its HTTP annotations, the bindings could be validated against specification/a2a.proto in a unit test, which would catch this class of drift automatically. Happy to open a PR for the one-line fix, the docstring, and/or that check if it would be useful — just say which you'd want.

Relevant log output

FAILED tests/compatibility/core_operations/test_requirements.py::test_must_requirement[STREAM-SUB-004-http_json]
  AssertionError: STREAM-SUB-004 [SubscribeToTask returns TaskNotFoundError for invalid taskId]
  failed on http_json: Expected error code 404 (TaskNotFoundError), got 405

# SUT implements the spec's `get: "/tasks/{id=*}:subscribe"`, so POST is rejected:
$ curl -i -X POST https://<sut>/a2a/v1/tasks/does-not-exist:subscribe
HTTP/1.1 405 Method Not Allowed

$ grep -n "http_json_method" tck/requirements/base.py | sed -n '6p'
150:    http_json_method=HTTP_POST,

$ grep -n 'subscribe' specification/a2a.proto
      get: "/tasks/{id=*}:subscribe"

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions