Skip to content

T-2MBO-e2e-servers-get-per-worktree-ports

State: open/ready · Impact: medium · Complexity: small

AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-09-27. Review the Goal, Approach, Today, Areas, and Acceptance-criteria carefully before trusting it.

Playwright e2e fixture servers bind a hardcoded, shared address instead of a per-worktree one. When two worktrees run the e2e suite at once, the second run’s Playwright attaches to the first run’s server and tests the wrong build. Each run should get its own server. Found during T-98C8-polish-lint-config-patch-save.

solutions/determined/playwright.config.ts’s webServer array starts every fixture server on a hardcoded 127.0.0.1:<port> address with reuseExistingServer: true: smoke on 15090, notes on 15091, vaults on 15092, fitness on 15104, and the polish-lint server on 15105. fitness already has a per-checkout override — FITNESS_E2E_PORT is read by both the config and solutions/determined/tests/ui/serve-fitness.ts to move it off 15104 when another checkout’s server holds that port — but the other four, including polish-lint, do not. When two worktrees run the e2e suite at the same time, the second run’s reuseExistingServer: true finds the first run’s server already listening on the shared port and attaches to it instead of starting its own, so the second worktree’s suite runs against the first worktree’s build.

Every fixture server in the webServer array gets its port from @sksizer/dev-ports (packages/ts/dev-ports), the repo’s lease-based port resolver. dev-ports.yaml already declares all five servers by name: e2e-smoke (15090), e2e-notes (15091), e2e-vaults (15092), e2e-fitness (15104) and e2e-polish-lint (15105). The resolver’s fall-forward exists for this case: the same project running twice, such as two worktrees. It takes an O_EXCL lease per candidate port, checks that nothing listens there, and searches upward inside the determined band. So two worktrees running the suite at once each get their own server, and neither reuses a server it did not start.

Do not derive a port from the worktree path or branch. @sksizer/dev-ports replaced exactly that scheme (a dev-port.sh that hashed $(pwd)), which claimed twelve ports twice.

  1. In playwright.config.ts, resolve the five names with resolveDevPorts from @sksizer/dev-ports, and build each webServer url and each matching projects[].use.baseURL from the result. The resolver is async and Playwright reads the config once at load time. solutions/determined is "type": "module", so top-level await in the config is the first thing to try.
  2. Pass each resolved port to its tests/ui/serve-*.ts launcher through the webServer[].env, as FITNESS_E2E_PORT does for serve-fitness.ts today. Replace FITNESS_E2E_PORT with the same mechanism, so every launcher reads its port the same way.
  3. Release the leases when the run ends: call each Resolution.release() in a globalTeardown or a process exit handler. A lease left behind only costs a later run one fall-forward step, so a crashed run is safe.
  4. Decide reuseExistingServer. With leases, a port this run resolved is one no other lease holder owns, so reuse can only attach to a server this worktree started earlier. Keep true if that holds; otherwise set it to false for the leased servers.
  5. Verify by running the polish project from two worktrees at once and confirming each starts its own polish-lint server on a distinct port.
AreaNote
solutions/determined/playwright.config.tswebServer array hardcodes every fixture server’s port; resolve them through @sksizer/dev-ports
packages/ts/dev-ports/resolve.tsresolveDevPort/resolveDevPorts and Resolution.release(); consumed here, not changed
dev-ports.yamlAlready declares the five e2e-* names; update the e2e-fitness note once FITNESS_E2E_PORT is gone
solutions/determined/tests/ui/serve-fitness.tsReads FITNESS_E2E_PORT today; switch to the shared port variable
solutions/determined/tests/ui/serve-polish-lint.ts, serve-smoke.ts, serve-notes.ts, serve-vaults.tsFixed ports today; read the resolved port from their env
  • AC-1: Running the e2e suite (or the polish project alone) from two separate worktrees at the same time starts two independent fixture servers on two different ports, not one worktree’s suite silently attaching to the other’s already-listening server.
  • AC-2: Every fixture server in solutions/determined/playwright.config.ts’s webServer array gets its port from resolveDevPort/resolveDevPorts under its dev-ports.yaml name, and its projects[].use.baseURL uses the same resolved port. No port is derived from the worktree path or branch.
  • AC-3: reuseExistingServer: true still lets a single worktree re-run its own suite against its own already-running server, but no longer causes a second worktree’s Playwright run to attach to a first worktree’s server.
  • Rewriting the sharing suite’s servers in playwright.sharing.config.ts — a separate config with its own two servers (:15093 / :15094), not touched here.
  • Changing how smoke’s PLATFORM_WEB_URL or mvux’s MVUX_WEB_URL external-server overrides work — those point the suite at an already-running dev server, a different use case from worktree isolation.
  • none

Spawned by /sdlc:spawn-task-pr on 2026-09-26 UTC from T-98C8-polish-lint-config-patch-save in https://github.com/sksizer/dev.

Bullet: The polish e2e port 15105 was held by another checkout’s Storybook; the suite ran only after a temporary, uncommitted port edit — the polish harness should read its port from an env var, as the fitness one does. Keywords searched: uncommitted, storybook, temporary, checkout, another, harness, fitness, polish Excluded: T-50QX-polish-rule-options-with-no-default Top candidates (score / status / headline):

  • 33 / open/ready / T-OLTA-task-work-step7-worktree-baseline-dir — task-work Step 7 resolves the quality baseline dir from a worktree
  • 29 / closed/obsoleted / T-521G-pilot-consumer-polish — Pilot the installed artifact on the polish repo end to end
  • 27 / closed/done / T-2CXH-scope-release-roster-off-moon-app-vitest — Release roster’s whole-repo bun test collects moon-managed apps’ vitest suites
  • 27 / planning/proposed / T-I71M-stories-agent-manager-workbench — Storybook mocks of the Agent Manager workbench
  • 26 / closed/done / T-ZEPK-harness-install-dev-link-mode — sdlc harness install claude: auto-detect dev mode + —link mode Decision: LINKED-EXISTING T-2MBO-e2e-servers-get-per-worktree-ports (override) Rationale: T-2MBO never surfaced in the automated top-5 — its body describes the general mechanism (another checkout’s already-listening server) rather than this bullet’s specific manifestation (Storybook holding port 15105), so keyword overlap was too thin to rank. Reading T-2MBO directly shows an exact match: the same hardcoded polish-lint port 15105 in solutions/determined/playwright.config.ts, spawned the same day from sibling task T-98C8’s post-mortem, proposing the same fix this bullet asks for (an env-var override following the FITNESS_E2E_PORT precedent). Still open (open/ready), unimplemented. Linked here instead of spawning a duplicate.

← Back to Tasks