Skip to content

feat: [ENG-2857] surface port-in-use error in brv webui#704

Open
ncnthien wants to merge 10 commits into
mainfrom
feat/ENG-2857
Open

feat: [ENG-2857] surface port-in-use error in brv webui#704
ncnthien wants to merge 10 commits into
mainfrom
feat/ENG-2857

Conversation

@ncnthien
Copy link
Copy Markdown
Collaborator

Summary

  • Replace the generic Failed to get web UI port. Use \brv restart`...with a specific message that points the user atbrv webui --port when the Web UI port (default 7700) is held by another process. Same guidance applies whether the conflict shows up at daemon boot (webui:getPort) or mid-run via --port (webui:setPort`).
  • New WebUiPortInUseError thrown by WebUiServer on EADDRINUSE. Daemon catches it, tracks the failure, and propagates a typed reason over the transport instead of swallowing or throwing a plain Error.
  • New src/shared/transport/events/webui-events.ts with WebuiEvents constant and discriminated-union response types replacing the inline {port?: number} shapes — TypeScript now narrows the CLI branches automatically.
  • Daemon log entries tightened so user-actionable guidance (--port, BRV_WEBUI_PORT) lives only in the CLI message — single source of truth.

Test plan

  • npm run typecheck — passes
  • npx eslint src/ — 0 errors on touched files
  • npx mocha "test/unit/core/domain/errors/webui-error.test.ts" "test/unit/infra/webui/**/*.test.ts" — 21 passing
  • Manual: hog 127.0.0.1:7700, run ./bin/run.js webui → boot-conflict message fires
  • Manual: hog 127.0.0.1:9090, run ./bin/run.js webui -p 9090 → setPort-conflict message fires
  • Manual: recovery — after boot conflict, brv webui --port <free> clears the failure state
  • Manual: regression — daemon spawn failure still routes through formatConnectionError (verified with BRV_IAM_BASE_URL='' ./bin/run.js webui)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Claude finished @ncnthien's task in 5m 18s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui

  • Read PR diff and understand scope
  • Review src/server/core/domain/errors/webui-error.ts
  • Review src/server/infra/webui/webui-server.ts
  • Review src/server/infra/daemon/brv-server.ts
  • Review src/oclif/commands/webui.ts
  • Review src/shared/transport/events/webui-events.ts
  • Review tests
  • Post inline comments for findings
  • Post summary with score

Summary

Overall: 4 / 5 — Solid, well-scoped UX improvement with proper typed errors and a clean discriminated-union transport contract. One real bug in the setPort failure path (misleading log + stale state) and a handful of small consistency/design polish items, all called out inline.

Highlights (what's good)

  • praise: Typed WebUiPortInUseError carrying the conflicting port cleanly replaces ad-hoc string matching at the daemon boundary, with handlers in brv-server.ts translating it into a transport-friendly discriminated response.
  • praise: Splitting webui-events.ts out of inline {port?: number} shapes is the right move — single source of truth, narrowing works, and the file slots neatly into AllEventGroups.
  • praise: Tests cover the new behavior end-to-end (port-in-use rejection, double-start guard, idempotent stop) and the error class is unit-tested.
  • praise: User-actionable guidance (--port, BRV_WEBUI_PORT) lives only in the CLI message — daemon logs stay terse. Good single-source-of-truth discipline.

Findings (see inline comments)

# File Severity Issue
1 src/server/infra/daemon/brv-server.ts:660–672 issue setPort stops the previous webui server before attempting the new port; on port_in_use the log says "keeping previous configuration" but in reality the daemon is now left with no Web UI running. Plus webuiBootFailure is not cleared on non-EADDRINUSE failures, causing stale port_in_use responses to later getPort calls.
2 src/server/infra/webui/webui-server.ts:32 nit "Already running" branch throws a plain Error while EADDRINUSE throws WebUiPortInUseError — inconsistent with the typed-error story.
3 src/oclif/commands/webui.ts:56–65 suggestion (style) resolvePortOrExit doesn't use return this.error(...) like the rest of the file. Also: not_started branch routes the user to brv restart with no log breadcrumb.
4 src/shared/transport/events/webui-events.ts:6–15 suggestion (design) Implicit discriminant ('port' in result) is fragile; a status: 'ok' | 'port_in_use' | 'not_started' field would survive future success variants.
5 test/unit/infra/webui/webui-server.test.ts:38–66 nit WebUiServer doesn't clear this.httpServer after the EADDRINUSE rejection — leaked handle until GC. Worth fixing in webui-server.ts and asserting the reset in this test.

Other notes (no inline)

  • Pre-existing: setupFeatureHandlers({ webuiPort: webuiServer?.getPort() }) at brv-server.ts:733 snapshots the port at startup, so a successful webui:setPort doesn't propagate the new port into PushHandler/StatusHandler review URLs. Not introduced by this PR — flagging for follow-up.
  • The 'error' listener on httpServer (webui-server.ts:38) isn't removed after listen() resolves; post-startup TCP errors will hit a Promise that's already settled. Pre-existing, harmless today, mentioned for completeness.

Verification

  • Inspected the diff against origin/main. Confirmed no other internal call sites consume webui:getPort / webui:setPort strings (only the event constants in webui-events.ts), so the response-shape change is safe.
  • Did not run typecheck/lint/tests in this review (read-only review).
    — Branch: feat/ENG-2857

Comment thread src/server/infra/daemon/brv-server.ts
Comment thread src/oclif/commands/webui.ts Outdated
Comment thread src/shared/transport/events/webui-events.ts Outdated
… typed already-running error, stale-state fix
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Claude finished @ncnthien's task in 4m 25s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 2)

  • Read PR diff (commits since prior review)
  • Verify prior findings addressed (status discriminator, typed already-running, stale state)
  • Re-review webui-error.ts
  • Re-review webui-server.ts (httpServer reset, already-running error type)
  • Re-review brv-server.ts (setPort stale-state + log fix)
  • Re-review webui.ts (status discriminator usage)
  • Re-review webui-events.ts (discriminator design)
  • Re-review tests
  • Post inline comments + summary

Summary

Overall: 4 / 5 — Round 1 feedback is almost fully addressed: explicit status discriminator is in, WebUiServerAlreadyRunningError replaces the plain Error, the misleading "keeping previous configuration" log is now accurate, and webuiBootFailure no longer goes stale on non-EADDRINUSE failures. One real regression introduced by the round-1 leak fix in webui-server.ts, and the deeper UX concern on setPort (probe-first) is still outstanding. Score holds at 4 because the new regression is a fairly narrow edge case.

What was addressed (prior round)

  • praise (resolved): WebUiServerAlreadyRunningError now thrown in the "already running" branch — typed-error story is consistent (webui-server.ts:32).
  • praise (resolved): resolvePortOrExit now uses return this.error(...) uniformly across all three branches (webui.ts:57–64). Reads cleanly.
  • praise (resolved): Explicit status: 'ok' | 'port_in_use' | 'not_started' discriminator replaces the implicit 'port' in result narrowing (webui-events.ts:6–15). Survives future success variants.
  • praise (resolved, half): Round-1 finding on setPort — misleading keeping previous configuration log is now accurate (Web UI unavailable), and webuiBootFailure is cleared on both success and non-EADDRINUSE failure so getPort no longer returns stale boot-time port info (brv-server.ts:660–675).
  • praise (resolved): Tests updated for WebUiServerAlreadyRunningError; new webui-error.test.ts covers the typed errors.

Findings this round (see inline comments)

# File Severity Issue
1 src/server/infra/webui/webui-server.ts:39 issue (correctness, blocking) The new this.httpServer = undefined line lives inside a persistent 'error' listener. Post-startup errors will null httpServer while running=true, wedging stop() (which guards on !this.httpServer). Gate the clear behind a resolved flag, or use once('error', ...) for the startup phase only.
2 src/server/infra/daemon/brv-server.ts:661–672 suggestion (non-blocking) Partial follow-up from round-1. Stale-state + log fixes are in, but the "probe new port before stopping old server" half wasn't picked up. Users running brv webui --port <busy> still lose their working Web UI on the previous port. Probe-first is strictly better; happy to defer.

Other notes (no inline, pre-existing)

  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) at brv-server.ts:734 still snapshots the port at startup. A successful webui:setPort does not propagate the new port into PushHandler / StatusHandler review URLs. Same call site as round 1 — flagging for follow-up, not introduced here.

Verification

  • Inspected the round-2 diff (commit 7045f28) and the current state of every changed file. Searched for any remaining inline webui:getPort / webui:setPort string literals outside webui-events.ts — none. Searched for stale 'port' in result narrowings — none.
  • Did not run typecheck / lint / test suite (read-only review). PR description states all three pass on the author's machine.
    — Branch: feat/ENG-2857

Comment thread src/server/infra/webui/webui-server.ts
Comment thread src/server/infra/daemon/brv-server.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Claude finished @ncnthien's task in 1m 57s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 3)

  • Read latest diff (commit 3b3896d)
  • Verify round-2 wedge fix in webui-server.ts (resolved-flag gating)
  • Verify new unit test asserts post-startup 'error' does NOT clear httpServer
  • Re-check brv-server.ts setPort path and other touched files for regressions
  • Post summary

Summary

Overall: 5 / 5 — Round-2 blocking issue is cleanly resolved. Score bumps from 4 → 5 because all blockers are now addressed and only the explicitly-deferred UX item remains (probe-then-stop in setPort, which both sides agreed is a follow-up).

What was addressed (round 2)

  • praise (resolved): The 'error' listener at webui-server.ts:39 is now gated behind a resolved flag that flips inside listen()'s callback. Post-startup TCP errors no longer null httpServer while running=true, so stop() is no longer wedged. Inverted the EADDRINUSE branch into a cleaner ternary — nice tidy-up.

    this.httpServer.on('error', (err: NodeJS.ErrnoException) => {
      if (resolved) return
      this.httpServer = undefined
      reject(err.code === 'EADDRINUSE' ? new WebUiPortInUseError(port) : err)
    })
    
    this.httpServer.listen(port, TRANSPORT_HOST, () => {
      resolved = true
      ...
    })
  • praise (resolved): New unit test at webui-server.test.ts:85–100 asserts the exact scenario from the round-2 finding: after start() resolves, emitting 'error' on the underlying HttpServer does not clear httpServer, isRunning() stays true, getPort() is unchanged, and stop() still completes cleanly. Matches the requested coverage 1:1.

Findings this round

None blocking. One small observation, no inline:

  • With the gated listener, post-startup server-level errors are now silently swallowed (the listener returns early, no logging). For the HttpServer-level 'error' event this is rare and acceptable (per-connection errors fire elsewhere), and round 2 explicitly called this trade-off out as fine. If observability into late server errors ever matters, a single log() call before return would do it — not a blocker for this PR.

Carried-over (acknowledged out of scope, no action needed)

  • brv webui --port <busy> still tears down the working server before probing the new port. Both reviewer and author agreed to defer to a follow-up; the L667 log now states reality, so the current behavior is at least honest.
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) snapshot at brv-server.ts:734 — pre-existing, not introduced here.

Verification

  • Inspected round-3 diff (commit 3b3896d) and current state of every changed file. Confirmed:
    • webui-events.ts properly registered in AllEventGroups (events/index.ts:95).
    • setPort handler (brv-server.ts:660–680) correctly clears webuiBootFailure on success (L675) and on non-port failure (L671), and sets it on port-in-use (L666).
    • resolvePortOrExit uses result.status === 'ok' narrowing consistently (webui.ts:57).
  • Did not run typecheck / lint / tests in this review (read-only). PR description states all pass on the author's machine.

LGTM — ready to ship.
— Branch: feat/ENG-2857

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Claude finished @ncnthien's task in 1m 24s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 4)

  • Read latest diff against origin/main
  • Verify state of changed files
  • Check for regressions since round 3
  • Post summary

Summary

Overall: 5 / 5 — Score holds from round 3. This synchronize event was triggered by the merge of main into the branch (commit 96337b3), not by new PR work. None of the PR-specific files changed since round 3 (commit 3b3896d); the diff vs. 3b3896d is entirely upstream main changes (curate telemetry handler + dream/idle dispatch rework in brv-server.ts, MigrateEvents export in events/index.ts).

Verification

  • git log 3b3896d..HEAD → only the merge commit (96337b3) and the merged PR (99fc4e5, ENG-2884 tool-mode dream).
  • git diff 3b3896d..HEAD -- <8 PR-touched files> — confirmed:
    • webui-server.ts, webui-events.ts, webui-error.ts, oclif/commands/webui.ts, webui-server.test.ts, webui-error.test.ts — byte-identical to round 3.
    • brv-server.ts — only upstream changes (idle policy simplification, new TaskCurateResultEvent handler). Webui block (L578–625) is byte-identical and still imports WebuiEvents / WebUiPortInUseError correctly.
    • events/index.ts — only the MigrateEvents export was added; webui-events.ts still exported and registered in AllEventGroups.
  • No merge conflicts left in the tree; git status clean.

Carry-over (already acknowledged, out of scope)

  • brv webui --port <busy> still tears down the working server before probing the new port (round 2 deferred). The L611 log states the reality (Web UI unavailable), so behavior is honest.
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) at brv-server.ts:678 still snapshots the port at startup — pre-existing, not introduced here.

LGTM — still ready to ship.
— Branch: feat/ENG-2857

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 30, 2026

Claude finished @ncnthien's task in 56s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 5)

  • Read latest diff against origin/main
  • Verify state of changed files since round 4
  • Check whether this synchronize introduced new PR work
  • Post summary

Summary

Overall: 5 / 5 — Score holds from rounds 3 and 4. This synchronize event was triggered by another merge of main into the branch (commit 8838e8e), not by new PR work. None of the 8 PR-touched files changed since round 4 (commit 96337b3).

Verification

  • git log 96337b3..HEAD → only 8838e8e (merge of main) and c498b2f (the merged sibling PR proj/byterover-tool-mode).
  • git diff --stat 96337b3..HEAD -- <8 PR-touched files> → empty (zero lines changed across all PR-relevant files).
  • The 28 files touched by the merge are all unrelated: curate session (oclif/lib/curate-session.ts), HTML render/reader, prompt/skill templates, and a new webui/features/migrate/ subtree. None of these interact with the webui-server, webui events, or the WebUiPortInUseError paths reviewed in rounds 1–3.

Carried-over (still acknowledged, out of scope)

  • brv webui --port <busy> still tears down the working server before probing the new port — deferred to a follow-up (round 2). The log at brv-server.ts honestly states the resulting state ("Web UI unavailable").
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) still snapshots the port at startup — pre-existing, not introduced here.

LGTM — still ready to ship.
— Branch: feat/ENG-2857

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Claude finished @ncnthien's task in 3m 51s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 6)

  • Identify what changed since round 5 (commit 8838e8e)
  • Check whether the new commit 6d3fa5d introduces PR-relevant work
  • Verify regressions / re-check touched files
  • Post inline comments + summary

Summary

Overall: 4 / 5 — Score drops one notch from round 5's 5/5. The new commit 6d3fa5d is a real, scoped feature ("auto-fallback Web UI to next port when default is in use") — implementation is correct and the typed-response surface absorbs the change cleanly, but the commit ships zero new tests, which conflicts directly with CLAUDE.md's "Testing (Strict TDD — MANDATORY)" section and its 80% / critical-paths-must-be-covered rule. There's also one UX design question worth resolving before this is "done done": whether a persisted explicit user preference should auto-fallback the same way the default does.

What this commit adds

  • Boot-time loop in brv-server.ts:241–279 tries webuiPreferredPort + offset for offset ∈ [0, 9] (10 attempts), stops on first success or non-EADDRINUSE error.
  • New webuiRequestedPort state captures the originally-asked port whenever a fallback fires.
  • WebuiGetPortResponse ok variant gains optional requestedPort: number so the CLI can surface "you asked for X, you got Y" to the user.
  • WEBUI_MAX_FALLBACK_ATTEMPTS = 10 constant exported from server/constants.ts.
  • getConfig() middleware now reads webuiServer?.getPort() ?? webuiPreferredPort so /api/ui/config reports the actual bound port (frontend gets the right Socket.IO host).

Findings (see inline)

# File Severity Issue
1 src/server/infra/daemon/brv-server.ts:241–279 issue (blocking per CLAUDE.md) Auto-fallback loop is the new critical path and has zero new tests. Suggest extracting to a startWebUiWithFallback helper and covering all four branches (success-on-first / success-on-Nth / all-busy / non-EADDRINUSE break).
2 src/server/infra/daemon/brv-server.ts:219–223 suggestion (UX, non-blocking) webuiPreferredPort conflates default (no intent) with BRV_WEBUI_PORT / persisted preference (explicit intent). A user whose stored preference is 9090 will silently get 9091 on next boot if 9090 is busy — they never asked for any port in [9090, 9099]. Gate fallback on the source.
3 src/server/infra/daemon/brv-server.ts:262 nit (UX) When all 10 ports are busy, conflictPort: webuiPreferredPort only names the first port, but the CLI message will read "port 7700 is already in use" — true but understates the situation. Consider carrying an attempted range.
4 src/server/infra/daemon/brv-server.ts:238 praise Reusing one WebUiServer instance across loop iterations works correctly thanks to round-3's httpServer = undefined clear on EADDRINUSE. One-line comment would help future readers.
5 src/oclif/commands/webui.ts:57–63 suggestion (style) 'requestedPort' in result is unnecessary — requestedPort lives on the ok variant directly, so the status === 'ok' narrowing already covers it. Matches the round-1 "no implicit in narrowing" feedback.

Carried-over (still acknowledged, out of scope here)

  • brv webui --port <busy> still tears down the working server before probing — explicitly deferred in round 2. Note that the new feature is intentionally boot-only and does not auto-fallback for --port, which is the right semantics for explicit user intent.
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) snapshot at the original brv-server.ts location — pre-existing.

Verification

  • git log 8838e8e..HEAD → only the new feature commit 6d3fa5d.
  • git diff 8838e8e..HEAD --stat → 4 source files touched, 0 test files.
  • Read every changed file in full and webui-state.ts for the persistence semantics behind finding Feat/refactor structure #2.
    — Branch: feat/ENG-2857

Comment thread src/server/infra/daemon/brv-server.ts Outdated
Comment thread src/server/infra/daemon/brv-server.ts Outdated
Comment thread src/server/infra/daemon/brv-server.ts
Comment thread src/server/infra/daemon/brv-server.ts
Comment thread src/oclif/commands/webui.ts
@DatPham-6996
Copy link
Copy Markdown
Collaborator

DatPham-6996 commented Jun 1, 2026

Hey @ncnthien , please have a look on port issue that current uses as IPv6 instead of IPv4, that doesn't cover in current code
Summarisation & suggestion fix:

Listener Bind address Stack
Docker container 11111 (host 7700 → Redis 6379) 0.0.0.0:7700 + [::]:7700 IPv6 wildcard
brv daemon (PID 80926) 127.0.0.1:7700 IPv4 only

brv's webui binds IPv4-only because server/constants.ts:43 sets TRANSPORT_HOST = '127.0.0.1'. Docker holds the IPv6 wildcard [::]:7700. Those two coexist at the OS level — there is no EADDRINUSE — so:

  1. webuiServer.start(7700) succeeds → GET_PORT returns {port: 7700, status: 'ok'} → CLI prints http://localhost:7700.
  2. But when your browser opens http://localhost:7700, macOS resolves localhost to both 127.0.0.1 and ::1, and browsers/curl prefer IPv6 ::1 first → they connect to Docker's Redis, not brv → blank/broken page.

Proof from your machine:


curl -4 localhost:7700/api/ui/config  → HTTP 200   (brv, works)

curl -6 localhost:7700/api/ui/config  → HTTP 000   (Docker, broken)

curl    localhost:7700  (browser path)→ ::1, HTTP 000

Why the ENG-2697 fix doesn't catch it

The conflict detection on this branch keys entirely off EADDRINUSEWebUiPortInUseErrorport_in_use (brv-server.ts:236-238, webui-server.ts:42). Because brv binds IPv4-specific and the other app holds the IPv6 wildcard, the OS never raises that error. The fix literally cannot see this collision, so it keeps reporting 7700 as ok.

Fix it now (pick one)

  • Move brv off 7700 (persists the choice): brv webui --port 8080
  • Free 7700: docker stop 11111 (it's forwarding host 7700 → a Redis on 6379 — you probably didn't intend it on 7700)
  • Quick proof it's the IPv6 split: open http://127.0.0.1:7700 instead of localhost — that hits brv directly.

The real code fix

Two complementary options, and the first is small + directly in the spirit of this branch ("use printed webui URL in skill guidance"):

  1. Print http://127.0.0.1:${port} instead of http://localhost:${port} in webui.ts:26 (and in buildReviewUrl). Since brv binds IPv4-only, the printed URL should target the stack it actually owns — that sidesteps the localhost→::1→Docker ambiguity entirely.
  2. Make conflict detection probe instead of trusting EADDRINUSE — before/after binding, attempt a connect to the target port and verify the responder is brv (e.g. /api/ui/config); if something foreign answers, fall through to port_in_use (or auto-increment). This catches cross-stack squatters that don't trigger EADDRINUSE.

Want me to implement #1 (the minimal, TDD'd fix), #2, or both?

Root cause: an IPv4 / IPv6 split on 7700 Port 7700 is not free, but brv can't tell. Two things are bound to it right now: Listener Bind address Stack Docker container 11111 (host 7700 → Redis 6379) 0.0.0.0:7700 + [::]:7700 IPv6 wildcard brv daemon (PID 80926) 127.0.0.1:7700 IPv4 only brv's webui binds IPv4-only because [server/constants.ts:43](vscode-webview://0j4knuqbc4fituqn7gq00jlrmhcvrt9a21vage3f3gh0dkoi8vhv/src/server/constants.ts#L43) sets TRANSPORT_HOST = '127.0.0.1'. Docker holds the IPv6 wildcard [::]:7700. Those two coexist at the OS level — there is no EADDRINUSE — so: 1. webuiServer.start(7700) succeeds → GET_PORT returns {port: 7700, status: 'ok'} → CLI prints http://localhost:7700. 2. But when your browser opens http://localhost:7700, macOS resolves localhost to both 127.0.0.1 and ::1, and browsers/curl prefer IPv6 ::1 first → they connect to Docker's Redis, not brv → blank/broken page. Proof from your machine:

curl -4 localhost:7700/api/ui/config → HTTP 200 (brv, works)
curl -6 localhost:7700/api/ui/config → HTTP 000 (Docker, broken)
curl localhost:7700 (browser path)→ ::1, HTTP 000
Why the ENG-2697 fix doesn't catch it
The conflict detection on this branch keys entirely off EADDRINUSE → WebUiPortInUseError → port_in_use (brv-server.ts:236-238, webui-server.ts:42). Because brv binds IPv4-specific and the other app holds the IPv6 wildcard, the OS never raises that error. The fix literally cannot see this collision, so it keeps reporting 7700 as ok.
Fix it now (pick one)

  • Move brv off 7700 (persists the choice): brv webui --port 8080
  • Free 7700: docker stop 11111 (it's forwarding host 7700 → a Redis on 6379 — you probably didn't intend it on 7700)
  • Quick proof it's the IPv6 split: open http://127.0.0.1:7700 instead of localhost — that hits brv directly.
    The real code fix
    Two complementary options, and the first is small + directly in the spirit of this branch ("use printed webui URL in skill guidance"):
  1. Print http://127.0.0.1:${port} instead of http://localhost:${port} in [webui.ts:26](vscode-webview://0j4knuqbc4fituqn7gq00jlrmhcvrt9a21vage3f3gh0dkoi8vhv/src/oclif/commands/webui.ts#L26) (and in buildReviewUrl). Since brv binds IPv4-only, the printed URL should target the stack it actually owns — that sidesteps the localhost→::1→Docker ambiguity entirely.
  2. Make conflict detection probe instead of trusting EADDRINUSE — before/after binding, attempt a connect to the target port and verify the responder is brv (e.g. /api/ui/config); if something foreign answers, fall through to port_in_use (or auto-increment). This catches cross-stack squatters that don't trigger EADDRINUSE.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Claude finished @ncnthien's task in 3m 38s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 7)

  • Identify what changed since round 6 (commit adbb0d3)
  • Review new start-webui-with-fallback.ts helper
  • Review its unit tests (5 cases, all critical branches)
  • Re-review brv-server.ts for the strict-vs-fallback split (explicitWebuiPort design)
  • Check webui.ts for the 'requestedPort' in result narrowing nit from round 6
  • Address @DatPham-6996's IPv4/IPv6 split observation
  • Post inline findings + summary

Summary

Overall: 5 / 5 — Round-6 blocking finding (no tests for the new auto-fallback critical path) and design nit (explicitWebuiPort collapsing default vs. user intent) are both cleanly addressed. The remaining items in this round are all non-blocking: a couple of small type/style polish opportunities and one carry-over observation from @DatPham-6996 about IPv4/IPv6 binding that's worth weighing but doesn't have to block this PR.

What was addressed (round 6)

  • praise (resolved): Loop extracted into start-webui-with-fallback.ts — pure function, stubbable via a minimal WebUiStarter interface, returns a discriminated StartWebUiOutcome. The // eslint-disable-next-line complexity annotation on main() is removed as a happy side effect.
  • praise (resolved): test/unit/infra/daemon/start-webui-with-fallback.test.ts covers all four critical branches from the round-6 finding (success-on-first / fallback-to-next / all-busy-EADDRINUSE / strict-mode) plus the non-EADDRINUSE break path. Test count for the webui area is now 27.
  • praise (resolved): explicitWebuiPort = webuiPortEnv ? ... : readWebuiPreferredPort() splits the priority chain so env-or-persisted intent stays strict (maxAttempts = 1) and only the unconfigured default falls back to neighbors. Matches the round-2 setPort design (explicit --port stays strict). Inline comment on this on brv-server.ts:225.
  • praise (resolved): 'requestedPort' in result is gone from webui.ts:58 — now plain result.requestedPort !== undefined. Matches round-1's "no implicit in narrowing" feedback.
  • praise (deferred but honest): Carrying the attempted range over the wire was acknowledged as a separate PR. The daemon log at brv-server.ts:256-260 now correctly says either "port X is already in use" (strict mode) or "ports X-Y are all in use" (fallback mode) depending on webuiMaxAttempts > 1. Honest reporting on the server side; CLI message narrowing is the deferred bit.

Findings this round (see inline)

# File Severity Issue
1 src/server/infra/daemon/start-webui-with-fallback.ts:7 suggestion (design, non-blocking) StartWebUiOutcome uses an implicit discriminator. Callers narrow with 'actualPort' in outcome — the same pattern the team rejected in round 1 for WebuiGetPortResponse. Adding status: 'ok' | 'error' would make the helper consistent with the rest of the typed-response story.
2 src/shared/transport/events/webui-events.ts:6-15 nit (type precision, non-blocking) Folding WebuiOkResponse into both response types now advertises an optional requestedPort on WebuiSetPortResponse that the SET_PORT handler never emits. Harmless at runtime, but a small future trap. Split the ok types per event or accept the looseness — either is fine.
3 src/oclif/commands/webui.ts:26 observation (IPv4/IPv6, weigh) Per @DatPham-6996: daemon binds 127.0.0.1 only, CLI prints http://localhost:.... On dual-stack hosts (macOS), localhost resolves to ::1 first, so an IPv6-wildcard squatter (Docker [::]:7700) silently steals the browser request without triggering EADDRINUSE — bypassing all the error-surfacing this PR adds. A one-line change to print http://127.0.0.1:${webuiPort} is a low-risk in-scope fix; probe-responder is a follow-up.
4 test/unit/infra/daemon/start-webui-with-fallback.test.ts:22-77 praise Exactly the requested coverage; in-memory + fast as CLAUDE.md mandates.
5 src/server/infra/daemon/brv-server.ts:219-225 praise Clean explicitWebuiPort split. UX is now coherent across env / persisted / default sources.

Carry-over (still acknowledged, out of scope)

  • brv webui --port <busy> still tears down the working server before probing — deferred since round 2. The L638 log honestly says "Web UI unavailable".
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) snapshot — pre-existing.
  • CLI port_in_use message names only conflictPort even when an entire range was attempted — deferred, carrying the range over the wire is a separate small PR.

Verification

  • git log 6d3fa5d..HEAD → only the new feature/refactor commit adbb0d3.
  • git diff 6d3fa5d..HEAD --stat → 5 files touched, 134 insertions / 40 deletions, including a real test file this round.
  • Read every changed file in full, plus server/constants.ts and webui-server.ts to confirm the IPv4-only TRANSPORT_HOST binding.
  • Did not run typecheck / lint / mocha in this read-only review.

LGTM — ready to ship. The IPv4/IPv6 observation is worth a brief judgment call before merging (one-line URL change or defer to a follow-up).

— Branch: feat/ENG-2857 ·

Comment thread src/server/infra/daemon/start-webui-with-fallback.ts Outdated
Comment thread src/shared/transport/events/webui-events.ts Outdated
Comment thread src/oclif/commands/webui.ts
Comment thread test/unit/infra/daemon/start-webui-with-fallback.test.ts
Comment thread src/server/infra/daemon/brv-server.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Claude finished @ncnthien's task in 3m 2s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 8)

  • Identify what changed since round 7 (commit 469796e)
  • Verify the 5 round-7 findings landed
  • Re-check webui.ts for the split resolveGetPort / resolveSetPort design
  • Re-check webui-events.ts for the precise per-event ok types
  • Re-check start-webui-with-fallback.ts and its tests for the discriminator
  • Confirm no regressions in other PR-touched files
  • Post inline findings + summary

Summary

Overall: 5 / 5 — All five round-7 findings are cleanly resolved with no regressions. The new commit (469796e) is small and tight (5 files, +40/-32) and every change is in service of the typed-response invariant established in round 1. Score climbs back from round 7's 5/5 hold to a confident 5/5.

What was addressed (round 7)

  • praise (resolved): StartWebUiOutcome now carries an explicit status: 'ok' | 'error' discriminator (start-webui-with-fallback.ts:7-9). brv-server.ts:243 narrows via webuiOutcome.status === 'ok', and the three test assertions at start-webui-with-fallback.test.ts:48,61,71 use outcome.status === 'error'. No more 'actualPort' in outcome / 'error' in outcome ducktyping.
  • praise (resolved): Per-event ok types in webui-events.ts:6-16WebuiGetPortOkResponse carries optional requestedPort, WebuiSetPortOkResponse does not. The type now reflects exactly what each handler emits, no phantom-field trap.
  • praise (resolved): webui.ts refactored into split resolveGetPort and resolveSetPort. Each handler owns its specific response type, so TS narrowing is per-event and the 'requestedPort' in result check is gone for good.
  • praise (resolved): webui.ts:25 prints http://127.0.0.1:${webuiPort} — matches the IPv4-only TRANSPORT_HOST binding. Resolves the IPv4/IPv6 split @DatPham-6996 flagged: dual-stack hosts no longer resolve localhost to ::1 and silently hit a foreign squatter instead of brv.
  • praise (resolved): Test runtime checks tightened to the explicit discriminator — outcome.status === 'error' instead of 'error' in outcome at three call sites.

Findings this round

None blocking. Posted four praise-only inline comments on the round-7 resolutions.

Carried-over (already acknowledged, out of scope)

  • brv webui --port <busy> still tears down the working server before probing — deferred since round 2. The brv-server.ts log honestly says "Web UI unavailable".
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) snapshot at daemon startup — pre-existing.
  • CLI port_in_use message names only conflictPort even when an entire range was attempted — author confirmed will track as a separate scoped PR.
  • Other localhost:-formatted URLs in the codebase (e.g. buildReviewUrl) may have the same IPv4/IPv6 ambiguity — author confirmed will audit in a follow-up. Worth a quick rg "http://localhost:" src/ pass when convenient.

Verification

  • git log adbb0d3..HEAD → only the new typed-response polish commit 469796e.
  • git diff adbb0d3..HEAD --stat → 5 files, +40/-32. Tests updated in the same commit.
  • Read every changed file in full plus brv-server.ts (the daemon's GET_PORT / SET_PORT handlers at L601-653) and webui-server.ts to verify the discriminator changes flow through correctly.
  • Did not run typecheck / lint / tests in this read-only review. PR description states all pass on the author's machine.

LGTM — ready to ship.

— Branch: feat/ENG-2857 ·

Comment thread src/oclif/commands/webui.ts
Comment thread src/oclif/commands/webui.ts Outdated
Comment thread src/shared/transport/events/webui-events.ts
Comment thread src/server/infra/daemon/start-webui-with-fallback.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Claude finished @ncnthien's task in 2m 16s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 9)

  • Identify what changed since round 8 (commit 77ba89f)
  • Review new/modified files
  • Check for regressions
  • Post inline findings + summary

Summary

Overall: 4 / 5 — Drops one notch from round 8's 5/5. Round 8 had a tidy IPv4 URL fix that closed @DatPham-6996's reported bug; this round reverts it back to http://localhost:... "per product preference." The product call is fine to make, but the underlying IPv4/IPv6-split bug DatPham documented in this very thread is now back without an alternative mitigation. Two small doc nits in the new MANUAL_TEST_ENG-2857.md (an internal inconsistency from the same revert, plus a repo-hygiene observation). None of the round 1–8 fixes regressed.

What changed in round 9

Single feature commit: 77ba89f — "revert CLI URL to localhost per product preference". Two files touched:

  • src/oclif/commands/webui.ts:25http://127.0.0.1:${webuiPort}http://localhost:${webuiPort}
  • MANUAL_TEST_ENG-2857.md — new manual-test runbook (164 lines, untouched elsewhere in the diff)

No test changes, no source-of-truth code logic changed.

Findings this round (see inline)

# File Severity Issue
1 src/oclif/commands/webui.ts:25 observation (regression, non-blocking — product call) Revert reopens DatPham-6996's dual-stack scenario: daemon binds IPv4-only, browser resolves localhost → ::1 first, an IPv6 squatter silently steals the connection without EADDRINUSE. The PR's entire error-surfacing pipeline is bypassed. If localhost is firm, please file a follow-up to either dual-stack-bind (TRANSPORT_HOST = '::') or probe /api/ui/config for a brv responder.
2 MANUAL_TEST_ENG-2857.md:159 issue (doc inconsistency) Pass criteria for Test 1 still reads URL is http://127.0.0.1:7700 but Test 1's expected output (L34) and Test 2's (L58) now say http://localhost:7700/7701. Leftover from the URL revert.
3 MANUAL_TEST_ENG-2857.md:1 suggestion (repo hygiene) First manual-test runbook tracked at the repo root in this project; same content already lives in the PR description's ## Test plan. Three options posted inline (delete before merge / move under docs/ / gitignore as personal scratchpad).

Carried-over (still acknowledged, out of scope)

  • brv webui --port <busy> still tears down the working server before probing — deferred since round 2.
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) snapshot at daemon startup — pre-existing.
  • CLI port_in_use message names only conflictPort even when a range was attempted — author tracking as a separate PR.
  • buildReviewUrl (server/utils/build-review-url.ts:4) still emits http://localhost:${webuiPort} — same IPv4/IPv6 ambiguity, author committed to a follow-up audit.

Verification

  • git log 469796e..HEAD → only the URL-revert commit 77ba89f.
  • git diff 469796e..HEAD --stat → 2 files, +165/-1.
  • Confirmed the daemon still binds IPv4-only by reading server/constants.ts:43 and webui-server.ts:45.
  • Confirmed no other PR-touched files changed since round 8 (all round 1–8 fixes still in place).
  • Did not run typecheck / lint / tests in this read-only review.

— Branch: feat/ENG-2857

Comment thread src/oclif/commands/webui.ts
Comment thread MANUAL_TEST_ENG-2857.md Outdated
Comment thread MANUAL_TEST_ENG-2857.md Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Claude finished @ncnthien's task in 1m 19s —— View job


Claude PR Review — feat: [ENG-2857] surface port-in-use error in brv webui (round 10)

  • Identify what changed since round 9 (commit 1bd56b5)
  • Verify state of changed files
  • Re-check round 9 findings (URL revert, MANUAL_TEST doc issues)
  • Post summary

Summary

Overall: 5 / 5 — Climbs back from round 9's 4/5. The single new commit (1bd56b5) takes round-9 finding #3's recommended option 1 — delete the manual-test runbook before merge — which also resolves finding #2 (doc inconsistency at MANUAL_TEST_ENG-2857.md:159) by removing the file entirely. Finding #1 (URL revert reopens DatPham's IPv4/IPv6 silent-squatter scenario) remains in place but was explicitly framed in round 9 as a product preference call, not a code-quality regression, so it should not depress the score on its own.

What changed in round 10

  • 1bd56b5"untrack MANUAL_TEST_ENG-2857.md (local QA artifact)" — single deletion (164 lines). No source/test/config changes (git diff --stat 77ba89f..HEAD -- src/ test/ is empty).

Findings this round

None.

Carry-over (still acknowledged, out of scope)

  • IPv4/IPv6 split (round 9 finding Feat/init #1): CLI prints http://localhost:${webuiPort} while the daemon binds IPv4-only (TRANSPORT_HOST = '127.0.0.1'). On dual-stack hosts an IPv6-wildcard squatter (Docker [::]:7700, an npm start on ::) silently steals the browser request without triggering EADDRINUSE, bypassing this PR's error-surfacing pipeline. Author kept localhost per product preference; recommend filing a follow-up to either dual-stack-bind (TRANSPORT_HOST = '::') or probe /api/ui/config for a brv responder.
  • Other localhost: URLs (buildReviewUrl, etc.): Same IPv4/IPv6 ambiguity. Author committed to a follow-up audit (rg "http://localhost:" src/).
  • brv webui --port <busy> still tears down the working server before probing — deferred since round 2; the log states reality.
  • setupFeatureHandlers({webuiPort: webuiServer?.getPort()}) snapshot — pre-existing.
  • CLI port_in_use message names only conflictPort even when a range was attempted — author tracking as a separate scoped PR.

Repo-hygiene note (non-blocking)

MANUAL_TEST_*.md is not added to .gitignore, so if the author regenerates the file locally during follow-up QA it would re-appear in git status unstaged. If this is a long-term workflow (local-only QA runbooks), a one-line MANUAL_TEST_*.md entry in .gitignore would prevent accidental re-tracking. Not a blocker — author already removed the file from the index, which is the important part for this PR.

Verification

  • git log 77ba89f..HEAD → only the cleanup commit 1bd56b5.
  • git show --stat 1bd56b5 → 1 file deletion, 164 lines removed, no other touched files.
  • git diff --stat 77ba89f..HEAD -- src/ test/ → empty (zero source/test changes).
  • grep -rn "MANUAL_TEST" --include="*.md" --include="*.ts" --include="*.json" . → no stale references anywhere.
  • ls MANUAL_TEST_ENG-2857.md → file is gone from the working tree.
  • Did not run typecheck / lint / tests in this read-only review.

LGTM — ready to ship.

— · Branch: feat/ENG-2857

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.

3 participants