fix(dev): honor explicit --port literally instead of silently offsetting by runtime index (#1079)#1661
fix(dev): honor explicit --port literally instead of silently offsetting by runtime index (#1079)#1661aidandaly24 wants to merge 4 commits into
Conversation
…ing by runtime index When running multiple `agentcore dev` runtimes, an explicit -p/--port was silently offset by the runtime's index in agentcore.json (basePort + index), so `agentcore dev -r AgentB -p 8788` bound 8789 with no explanation. Clients targeting http://localhost:<-p>/invocations silently hit the wrong port. Use Commander's getOptionValueSource to distinguish explicit vs default -p: honor an explicit --port literally across the --logs, web-ui, and TUI paths; when -p is implicit (default), keep the index offset but log it (mirroring the existing findAvailablePort shift message) and document it in --help. invoke continues to target whatever port the server bound, made consistent with the chosen server semantics. Scope: Surgical, unit-test-verified. tsc --noEmit clean, eslint clean (no new warnings), 1002/1002 unit tests pass across src/cli/operations/dev and src/cli/tui including the updated and new getAgentPort tests. The only failing tests (4 in dev.test.ts) are pre-existing CLI-spawn failures that fail identically on the clean baseline (require a built dist; environmental, unrelated to this change). The web-ui path is a structurally multi-agent server, so for the explicit -p case the SELECTED runtime binds exactly -p while concurrently-served runtimes are offset relative to it -- the minimal way to honor explicit -p there without redesigning the multi-agent port model.
…ing by runtime index (aws#1079)
|
Claude Security Review: no high-confidence findings. (run) |
Package TarballHow to installgh release download pr-1661-tarball --repo aws/agentcore-cli --pattern "*.tgz" --dir /tmp/pr-tarball
npm install -g /tmp/pr-tarball/aws-agentcore-0.21.0.tgz |
agentcore-cli-automation
left a comment
There was a problem hiding this comment.
Thanks for picking this up — the core getAgentPort(..., explicit) change is clean and the unit tests for the explicit-honored case are solid. A few issues to address before merge:
- Missing fail-fast on conflict for explicit
-p(HTTP). This is the headline behavior promised in the PR description ("Honor explicit --port literally and fail fast on conflict") but it isn't implemented for HTTP agents —findAvailablePortsilently picks a different port. This reproduces the same class of silent-rebind surprise the PR is trying to fix. See inline comments oncommand.tsx,useDevServer.ts, andhandlers/start.ts. - Misleading log message when the default offset kicks in (
Port X in use as base...) — the port isn't actually in use, it's just being skipped for index reasons. - No tests for the new branch in
handlers/start.ts(the most subtle logic in the PR). - Confusing offset semantics for non-selected runtimes in the web UI when
-pis explicit (can produce ports below the user-requested base). - Telemetry (non-blocking). Per
src/cli/telemetry/README.md, please consider adding aport_explicit: z.boolean().optional()attribute toDevAttrsinsrc/cli/telemetry/schemas/command-run.tsand setting it fromcommand.tsx. The bug being fixed here is about silent misuse of-p; instrumenting explicit-vs-default would let us validate adoption of the new behavior in the wild.
Details inline.
| const fixedPort = isA2A ? 9000 : isMcp ? 8000 : getAgentPort(project, config.agentName, port); | ||
| const fixedPort = isA2A ? 9000 : isMcp ? 8000 : getAgentPort(project, config.agentName, port, portExplicit); | ||
| if (!isA2A && !isMcp && !portExplicit && fixedPort !== port) { | ||
| console.log(`Port ${port} in use as base, using ${fixedPort} for ${config.agentName}`); |
There was a problem hiding this comment.
Misleading message. This fires when !portExplicit && fixedPort !== port, i.e. the default base plus a non-zero runtime index. The text Port ${port} in use as base, using ${fixedPort} implies a port conflict — but nothing is actually in use; the port is just being skipped because of the runtime index.
The PR description references mirroring an existing "shift message," but the original message at the old command.tsx:417-418 was Port ${fixedPort} in use, using ${actualPort} (about a real availability fallback). The new message conflates two different events.
Suggest something like: Runtime "${config.agentName}" is at index ${idx}; using port ${fixedPort} (pass --port ${fixedPort} explicitly to override).
There was a problem hiding this comment.
Fixed in 8c56f8f. Reworded to Runtime "${config.agentName}" is at index ${idx}; using port ${fixedPort} (pass --port ${fixedPort} to override). — it no longer implies a conflict and points at the index + the explicit-override escape hatch.
| const isA2A = config.protocol === 'A2A'; | ||
| const isMcp = config.protocol === 'MCP'; | ||
| const fixedPort = isA2A ? 9000 : isMcp ? 8000 : getAgentPort(project, config.agentName, port); | ||
| const fixedPort = isA2A ? 9000 : isMcp ? 8000 : getAgentPort(project, config.agentName, port, portExplicit); |
There was a problem hiding this comment.
Missing fail-fast on explicit-port conflict (HTTP). Two lines below this (the unchanged const actualPort = await findAvailablePort(fixedPort) on the old line 412), HTTP agents will silently bind to whatever findAvailablePort returns. The A2A/MCP throw that follows it doesn't apply.
The PR description states the fix is to "honor explicit --port literally and fail fast on conflict," but for HTTP agents this code path silently rebinds. If a user runs agentcore dev -r AgentB -p 8788 --logs and 8788 is taken, they'll silently bind to 8789 — the exact silent-port-shift behavior this PR is trying to eliminate.
Options:
- Add an explicit branch: when
portExplicit && !isA2A && !isMcp && actualPort !== fixedPort, throw aValidationError(mirroring the A2A/MCP message). - Alternatively, skip
findAvailablePortentirely whenportExplicitis true for HTTP and try to bind directly, surfacing EADDRINUSE.
The same fix needs to be applied to useDevServer.ts (around line 186-188) and handlers/start.ts (around line 115).
There was a problem hiding this comment.
Fixed in 8c56f8f. Added an explicit-conflict branch in all three paths: command.tsx now throws a ValidationError when isHttp && portExplicit && actualPort !== fixedPort; useDevServer.ts sets error status + returns instead of warning; handlers/start.ts returns { success: false, error } for the selected runtime. So dev -r AgentB -p 8788 --logs with 8788 taken now fails fast instead of silently binding 8789.
| fixedPort ?? | ||
| (ctx.options.agentBasePort !== undefined | ||
| ? ctx.options.agentBasePort + (safeAgentIndex - (selectedIndex >= 0 ? selectedIndex : 0)) | ||
| : ctx.options.uiPort + 1 + safeAgentIndex); |
There was a problem hiding this comment.
Two concerns with this branch:
-
Unexpected offsets for non-selected agents. When the user runs
agentcore dev -p 8788 -r AgentB(AgentB at index 1),selectedIndex=1. The selected agent gets8788 + (1-1) = 8788(good). But if the user then clicks AgentA (index 0) in the UI, it tries to bind8788 + (0-1) = 8787— below the port the user asked for. And AgentC (index 2) gets 8789, which the user never mentioned. The "relative to selected" offset is non-obvious and produces ports the user didn't request (potentially even privileged ports if-pis low and another agent has a lower index).Consider one of:
- Use
agentBasePort + safeAgentIndexfor all runtimes (treat the explicit-pas the new base for the project; the selected runtime is no longer guaranteed to bind literally to-punless it's index 0). - Honor
-ponly for the selected runtime and use the defaultuiPort + 1 + indexallocation for the others. - At minimum, document the current behavior clearly so users aren't surprised.
- Use
-
No fail-fast on explicit-port conflict. Same issue as
command.tsx:findAvailablePort(targetPort)on the next line silently picks a different port. WhenagentBasePortcame from an explicit-p, this should error instead of silently rebinding (at least for the selected runtime whereagentBasePortmatches the user-typed value).
Also — this branch has no test coverage. Please add a unit test for doStartAgent's port resolution covering: (a) explicit agentBasePort honored for selected runtime, (b) other runtimes' relative offset, (c) fixed-port (A2A/MCP) still wins, (d) explicit-port-conflict behavior.
There was a problem hiding this comment.
Fixed in 8c56f8f. Took the second option you suggested: explicit -p is now honored literally only for the selected runtime; every other runtime keeps the default uiPort + 1 + index allocation, so we never produce ports below the requested base or for runtimes the user didn't name. The logic is extracted into a pure resolveAgentTargetPort helper with unit tests in web-ui/__tests__/start-port.test.ts covering: default allocation, agent-not-found, A2A/MCP fixed ports, explicit-honored selected runtime, and non-selected runtimes keeping the default. Explicit-port conflict for the selected runtime now fails fast (see the other thread).
| // HTTP: honor an explicit -p literally; otherwise offset by the runtime index | ||
| // so parallel runtimes bind distinct ports (consistent with the --logs path). | ||
| const httpPort = getAgentPort(project, config.agentName, targetPort, options.portExplicit); | ||
| const fixedPort = isA2A ? 9000 : isMcp ? 8000 : httpPort; |
There was a problem hiding this comment.
Missing fail-fast on explicit-port conflict. Resolving fixedPort here is fine, but the downstream code (around the unchanged line 186-188) calls findAvailablePort(fixedPort) and just emits an addLog('warn', ...) when the result differs. When options.portExplicit is true, this is the same silent-port-shift bug the PR claims to fix, just in the TUI path.
When portExplicit && !isRestart && port !== fixedPort, this should set status to error and surface a clear message (mirroring the A2A/MCP error branch above), not just warn.
There was a problem hiding this comment.
Fixed in 8c56f8f. In the HTTP branch, when options.portExplicit && !isRestart && port !== fixedPort we now addLog('error', ...), setStatus('error') and return, mirroring the A2A/MCP error branch — no more silent rebind in the TUI path. Restart still reuses the prior port as before.
|
Claude Security Review: no high-confidence findings. (run) |
|
Reviewed the diff against the four touched code paths (
No additional issues to flag. Deferring to the author to address the existing review. |
…instrument port_explicit - throw/error instead of silently rebinding when an explicit -p is taken across the --logs, TUI, and web-ui start paths (the silent-shift behavior aws#1079 removes) - reword the implicit-offset log to name the runtime index and how to override (no longer implies a port conflict) - web-ui: honor explicit -p literally only for the selected runtime; other runtimes keep the default uiPort+1+index allocation (no ports below the base) - extract resolveAgentTargetPort and unit-test it - add optional port_explicit telemetry attr to DevAttrs
|
Thanks for the thorough review — all five points addressed in 8c56f8f (on top of the prettier fix in 1ecc5a2):
Local: prettier clean, eslint (0 errors), typecheck, build, and |
|
Claude Security Review: no high-confidence findings. (run) |
agentcore-cli-automation
left a comment
There was a problem hiding this comment.
Re-reviewed after 8c56f8f. The five substantive issues raised in the prior review are all addressed:
- Fail-fast on explicit
-pconflict (HTTP) — confirmed incommand.tsx:430-434(ValidationError),useDevServer.ts:190-197(error log + setStatus + early return), andhandlers/start.ts:153-160(failure result returned before runningAgents is mutated). ✓ - Misleading offset log — reworded to
Runtime "<name>" is at index <idx>; using port <fixedPort> (pass --port <fixedPort> to override).No longer implies a conflict. ✓ - Non-selected runtime offset surprise —
handlers/start.tsnow keeps the defaultuiPort + 1 + indexallocation for non-selected runtimes; only the selected runtime honorsagentBasePortliterally. No more sub-base or unrequested-runtime ports. ✓ - Test coverage for
start.ts—resolveAgentTargetPortextracted as a pure helper and covered byweb-ui/__tests__/start-port.test.ts(default allocation, agent-not-found, A2A/MCP fixed, explicit-honored selected, non-selected default). ✓ - Telemetry —
port_explicit: z.boolean().optional()added toDevAttrsand set in the dev-server recorder. ✓
Also verified:
command.getOptionValueSource(port) === cli || envis the right way to distinguish explicit vs default with Commander. The option has no.env()binding, but treating both as explicit is defensible and future-proof.getAgentPort’s new fourth param defaults tofalse, so the existing two-arg callsites and tests still pass.agentBasePortis plumbed end-to-end:command.tsx→runBrowserMode(portExplicit ? port : undefined) →WebUIOptions.agentBasePort→RouteContext.options→resolveAgentTargetPort.- A2A/MCP framework-fixed ports (9000/8000) still take precedence over an explicit
-pin all three paths, which is the correct behavior.
Minor (non-blocking) observations, no changes required:
- In multi-runtime browser mode with no
-rbut explicit-p, the explicit port is effectively dropped becauseselectedAgentis undefined. That’s consistent with the new "honor for the selected runtime only" semantics and matches the--logspath which requires-ranyway. - The
port_explicitrecorder is only set on the server-start telemetry, not on the invoke/exec branches. That’s fine for adoption tracking since the binding happens on the server path.
LGTM.
Description
#1079
When running multiple
agentcore devruntimes in parallel, the-p/--portvalue is silently offset by the runtime's index in agentcore.json (basePort + index).agentcore dev -r AgentB -p 8788actually binds 8789, with no log explaining it. Downstream services/integration tests/CI scripts that target http://localhost:<-p>/invocations silently hit the wrong port or get connection refused; reordering runtimes in agentcore.json silently remaps every developer's ports. Local-dev only — no deployed/production surface.Fix
Honor explicit --port literally and fail fast on conflict; when -p is implicit (default), keep the index offset but LOG it (mirror the existing command.tsx:417-418 shift message) and document it in --help. Use Commander's getOptionValueSource to distinguish explicit vs default. Apply consistently across the three server-side offset pathways: command.tsx:402 (--logs), web-ui/handlers/start.ts:103 (uiPort+1+index — this path ignores -p entirely today), and useDevServer.ts:157 (TUI path, which currently honors -p with NO offset — divergent). NOTE: command.tsx:262 (invoke) offset is NOT a bug to remove — invoke must target whatever port the server bound; make it consistent with the chosen server semantics, don't strip it. Closed (unmerged) PR #1158 implements this approach and can be revived/rebased; it also preserves getAgentPort as a deprecated wrapper for backwards-compat.
This fix was produced by an automated triage+fix run and validated locally (build + unit suite + symptom reproduction). Opened as a draft for CI and human review; will be bug-bashed before it's marked ready.
Related Issue
Closes #1079
Documentation PR
N/A — bug fix; no devguide change required.
Type of Change
Testing
How have you tested the change? (full validation in PR comments after bug bash)
npm run test:unitandnpm run test:integnpm run typechecknpm run lintsrc/assets/, I rannpm run test:update-snapshotsand committed the updated snapshots — N/A, nosrc/assets/changesChecklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the
terms of your choice.