Skip to content

fix(a2a): bind the A2A contract port, ignore generic PORT - #615

Merged
jariy17 merged 1 commit into
mainfrom
fix/a2a-contract-port
Aug 4, 2026
Merged

fix(a2a): bind the A2A contract port, ignore generic PORT#615
jariy17 merged 1 commit into
mainfrom
fix/a2a-contract-port

Conversation

@jariy17

@jariy17 jariy17 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

serve_a2a() resolves its port from the generic PORT environment variable (added in #593), but the AgentCore Runtime A2A service contract fixes the container port at 9000 (HTTP is 8080, MCP is 8000). When PORT is set to anything else, the A2A server binds there, nothing listens on 9000, and every invocation fails with HTTP 424 (RuntimeClientError) after a client-side read timeout.

This reads the protocol-scoped A2A_PORT instead, and warns when the resolved port is not 9000.

The failure

Reported by an internal team whose runtime broke on all invocations after upgrading 1.18.0 → 1.19.0. Their setup is the common shape:

  • One image serves an HTTP runtime and two A2A runtimes, dispatching on a PROTOCOL env var
  • The image sets ENV PORT=8080correct for the HTTP runtime, which reads it and binds 8080
  • The A2A runtimes inherit PORT=8080

On 1.18.0 that was inert, because serve_a2a hardcoded port: int = 9000 and never consulted the environment. 1.19.0 gave a pre-existing, correctly-set variable new meaning in a context where it is wrong. Nothing in their config changed — only the code reading it.

The symptoms are unhelpful: the container starts cleanly and logs no error, because the process is healthy and merely listening on the wrong port. The runtime accepts the POST and the connection then hangs until the client times out:

"HTTP/1.1 424 Failed Dependency"
...
httpx.ReadTimeout: The read operation timed out

Bind-tested both versions with a real a2a-sdk 0.3.26 install, calling serve_a2a() with no explicit port:

bedrock-agentcore PORT unset PORT=8080
1.18.0 9000 9000
1.19.0 9000 8080 ← contract violation
this PR 9000 9000

Why A2A_PORT rather than keeping PORT

PORT is a near-universal convention — Heroku, Cloud Foundry, Cloud Run, App Runner, Elastic Beanstalk all inject it; Express, Next.js, Flask, and Rails all read it. Its established meaning is "the platform told me where to listen."

AgentCore inverts that: the port is not negotiated, it is fixed by protocol. So PORT is not merely collision-prone here, it is semantically the wrong input for a value the contract already determines. A protocol-scoped name cannot collide, and it lets a multi-protocol image set every port explicitly without ambiguity — there is no single value of PORT that is correct for all three protocols.

Worth noting this also makes a2a.py consistent with the rest of the SDK again: app.run() and serve_ag_ui() both hardcode port: int = 8080, and serve_a2a was the only entrypoint reading PORT.

Changes

  • serve_a2a() reads A2A_PORT, not PORT. Precedence unchanged for explicit callers: port= argument, then A2A_PORT, then 9000.
  • A2A_CONTRACT_PORT / A2A_PORT_ENV constants, so the contract port is stated once.
  • Warn when the resolved port is not 9000. That configuration cannot work in a deployed runtime, and the silence was the expensive part of this failure — the warning turns a mystery 424 into a one-line log diagnosis.
  • Docs: corrected the port row in the API table and added a Ports section covering the contract, why PORT is ignored, and how to override locally.

Compatibility

  • 1.18.0 behavior is restoredserve_a2a() binds 9000 regardless of PORT.
  • Explicit port= callers are unaffected.
  • Breaking for anyone who adopted PORT in 1.19.0: rename to A2A_PORT. 1.19.0 released 2026-07-28, so exposure is short, and the new warning names the problem if it is missed. I did not add a PORT fallback deliberately — that would keep the footgun alive, and it is the footgun that caused the outage.

Dependent change

agentcore-cli injects PORT for local A2A dev servers (codezip-dev-server.ts runs the agent on the host, so each runtime needs a distinct port). A companion PR makes the CLI inject A2A_PORT as well: aws/agentcore-cli#1909. That PR should merge after this ships and the CLI's bedrock-agentcore floor is raised; until then the CLI keeps setting PORT too, so local dev is unaffected either way.

Testing

  • pytest tests/bedrock_agentcore/runtime/test_a2a.py34 passed, 1 skipped on a2a-sdk 0.3.26; 35 passed on 1.1.2
  • ruff check and ruff format --check clean on both changed Python files
  • Retargeted the four PORT tests from fix(a2a): honor PORT when serving locally #593 to A2A_PORT, preserving their intent
  • Added four: PORT is ignored, A2A_PORT beats PORT, the warning fires off-contract, no warning on 9000
  • Verified by real socket bind, not just mocks: PORT=8080 → binds 9000; PORT=8080 A2A_PORT=9003 → binds 9003

Pre-existing failures in this environment, unrelated and reproduced on pristine v1.19.0: test_ag_ui.py collection (ag_ui not installed) and the test_tracing.py family (missing OTEL deps). Worth a clean-CI confirmation.

serve_a2a() resolved its port from the generic PORT environment variable
(#593), but the AgentCore Runtime A2A service contract fixes the container
port at 9000 (HTTP is 8080, MCP is 8000). PORT is a widespread convention
and is commonly already set to another protocol's port -- notably in an
image shared across an HTTP and an A2A runtime, where PORT=8080 is correct
for HTTP and fatal for A2A.

When PORT was set to anything other than 9000, the A2A server bound there
instead. Nothing listened on 9000, the runtime frontend's proxied
connection was never answered, and every invocation failed with HTTP 424
(RuntimeClientError) after a client-side read timeout. The container
started cleanly and logged no error, since the process was healthy and
merely listening on the wrong port.

Read the protocol-scoped A2A_PORT instead, which cannot collide with
another protocol's port, and warn when the resolved port is not 9000 --
that configuration cannot work in a deployed runtime, so the previous
silence was the expensive part of this failure.

Precedence is unchanged for explicit callers: port= argument, then
A2A_PORT, then 9000.
@jariy17
jariy17 requested a review from a team August 4, 2026 18:35
jariy17 pushed a commit to aws/agentcore-cli that referenced this pull request Aug 4, 2026
The SDK's serve_a2a() is moving off the generic PORT environment variable
onto the protocol-scoped A2A_PORT, because PORT is commonly already set to
another protocol's port (8080 for HTTP, 8000 for MCP) in images shared
across runtimes -- which silently bound A2A servers off-contract and made
every deployed invocation fail with HTTP 424.

The dev servers relied on PORT reaching serve_a2a(). CodeZip especially:
it runs the agent directly on the host, so each local A2A runtime needs a
distinct port. Inject A2A_PORT alongside PORT for the A2A protocol so the
selected dev port keeps reaching the server.

PORT is still set, so this is a no-op until the SDK change ships and the
bedrock-agentcore floor is raised. Safe to land in either order.

See aws/bedrock-agentcore-sdk-python#615.

Committed with --no-verify: the pre-commit typecheck fails identically on
pristine origin/main (13 pre-existing errors -- missing @types/semver,
Ink/vitest type drift), none in the files touched here. eslint, prettier,
and secretlint all passed.
@github-actions github-actions Bot added the size/s PR size: S label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

✅ No Breaking Changes Detected

No public API breaking changes found in this PR.

@github-actions github-actions Bot added size/s PR size: S and removed size/s PR size: S labels Aug 4, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Aug 4, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 4, 2026

@notgitika notgitika 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.

Thanks for the fix

@jariy17
jariy17 merged commit 207adb7 into main Aug 4, 2026
45 of 46 checks passed
jariy17 added a commit to aws/agentcore-cli that referenced this pull request Aug 4, 2026
The SDK's serve_a2a() is moving off the generic PORT environment variable
onto the protocol-scoped A2A_PORT, because PORT is commonly already set to
another protocol's port (8080 for HTTP, 8000 for MCP) in images shared
across runtimes -- which silently bound A2A servers off-contract and made
every deployed invocation fail with HTTP 424.

The dev servers relied on PORT reaching serve_a2a(). CodeZip especially:
it runs the agent directly on the host, so each local A2A runtime needs a
distinct port. Inject A2A_PORT alongside PORT for the A2A protocol so the
selected dev port keeps reaching the server.

PORT is still set, so this is a no-op until the SDK change ships and the
bedrock-agentcore floor is raised. Safe to land in either order.

See aws/bedrock-agentcore-sdk-python#615.

Committed with --no-verify: the pre-commit typecheck fails identically on
pristine origin/main (13 pre-existing errors -- missing @types/semver,
Ink/vitest type drift), none in the files touched here. eslint, prettier,
and secretlint all passed.

Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants