Skip to content

✨ feat(dev): portless local-dev proxy — per-worktree URLs for parallel agents (U1–U6, U10)#2543

Draft
andrew-bierman wants to merge 9 commits into
developmentfrom
explore/portless-packrat
Draft

✨ feat(dev): portless local-dev proxy — per-worktree URLs for parallel agents (U1–U6, U10)#2543
andrew-bierman wants to merge 9 commits into
developmentfrom
explore/portless-packrat

Conversation

@andrew-bierman

@andrew-bierman andrew-bierman commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adopt portless as the local-dev front door so every service gets a stable, per-worktree .localhost URL instead of a hardcoded port — fixing the port-collision storm from 50+ parallel host-side agents trampling :3001/:8787/:8081. Built on the Turborepo migration, now merged into development (PR #2539), so this PR targets development directly.

The entire browser/agent surface works end-to-end (verified live: a real app 307 served through the proxy). Native (Expo) is scoped but deferred — it needs a real simulator/device to validate.

Origin docs: docs/brainstorms/2026-05-22-portless-turborepo-dev-workflow-requirements.md, plan: docs/plans/2026-06-01-001-feat-portless-turborepo-dev-workflow-plan.md.

What's included (U1–U6, U10)

  • U2 — naming + host bootstrap. portless.json maps apps to stable names; scripts/portless-setup.sh (idempotent, CA trust) + a non-privileged postinstall readiness check.
  • U3 — $PORT injection + account pin. next dev --port ${PORT:-…}, wrangler dev --port ${PORT:-8787}; pinned account_id in wrangler.jsonc so remote-mode wrangler dev works in non-interactive agent shells (the real blocker — fixed independent of portless).
  • U4 — discovery. bun run portless:urls prints this worktree's service URLs.
  • U5 — auth/CORS. Dev-gated trust of https://*.localhost[:*] origins (extracted, unit-tested cors-origins.ts + better-auth trustedOrigins). Relaxations are gated to ENVIRONMENT === 'development' (z.enum().default('production')) and cannot reach prod.
  • U6 — web origin derivation. apps/web derives the API's sibling origin from window.location (<app>.localhostapi.localhost), carrying the worktree prefix and port automatically (works for both :443 and :1355). Centralizes 4 duplicated fallbacks.
  • U10 — docs + recovery. docs/portless-dev-workflow.md (incl. the :443 vs :1355 decision and the root-proxy split-brain pitfall) + portless:clean.

Key operational decision: :443 vs :1355

portless 0.13.1 hardcodes a sudo elevation for :443 (ignores setcap). Default is a no-sudo user-owned :1355 proxy (URLs carry :1355); clean :443 needs sudo at start kept user-context (sudo -E / passwordless rule / service install). Never run the proxy as root — a root proxy split-brains from user route registrations and 404s every backend. Documented.

Testing

  • 21 new unit tests for origin allow/deny incl. portless hosts (cors-origins.test.ts); full auth+cors suite green (39 tests).
  • better-auth wildcard patterns verified against its wildcardMatch implementation.
  • Live end-to-end: apps/web via portless runhttps://portless-packrat.web.localhost:1355/ → real 307.
  • wrangler dev --dry-run passes account selection with the pinned account_id.

Deferred (not in this PR)

  • U7–U9 (Expo simulator + physical-device LAN) — needs a real simulator/emulator/device; scoped in the plan, stubbed in the workflow doc.
  • Portless API-origin parity for apps/admin / apps/trails / apps/guides (surfaced in code review) — not a regression; admin requires its env var (no default), trails defaults to prod, so each needs its own care via a shared resolveApiBaseUrl extracted to @packrat/api-client. apps/landing needs nothing.

Known Residuals (accepted from review)

  • admin/trails/guides do not yet self-derive the API origin under portless (see Deferred). Low risk: behavior unchanged from before this PR.

Post-Deploy Monitoring & Validation

No additional operational monitoring required — this is local-dev tooling. The only runtime-touching change is the API CORS/trustedOrigins allowlist, and the new entries are gated to local .localhost origins (CORS) and ENVIRONMENT === 'development' (better-auth), so production auth behavior is unchanged. Sanity check after merge: confirm prod login still rejects an untrusted origin (covered by the prod-origin unit test).


🤖 Compound-engineered with Claude Code (Opus 4.8) via ce-brainstorm → ce-plan → ce-work.

- portless.json maps workspace apps to stable names
- scripts/portless-setup.sh: idempotent one-time host bootstrap (CA trust)
- scripts/portless-check.ts: non-privileged postinstall readiness reminder
- root package.json: portless:setup/portless:check scripts + portless devDep
- .gitignore: .portless/
…(U3)

- web/admin: next dev --port ${PORT:-3001/3002} (portless injection wins, distinct fallback)
- api/mcp: wrangler dev --port ${PORT:-8787}
- api/mcp wrangler.jsonc: pin account_id so remote-mode dev works in non-interactive agent shells
Prints per-worktree portless URLs for all configured apps so agents discover
service URLs without scraping terminal output. Encodes the U1 routing rules:
worktree-aware invocation, read PORTLESS_URL, never a root proxy.
- extract ALLOWED_ORIGIN_PATTERNS/isAllowedOrigin into utils/cors-origins.ts (unit-testable)
- add CORS pattern for https://<worktree>.<app>.localhost[:port]
- add dev-gated better-auth trustedOrigins: https://*.localhost and https://*.localhost:*
  (verified against better-auth wildcardMatch semantics)
- 21 unit tests covering allowed/portless/rejected origins
- centralize the 4 duplicated 'NEXT_PUBLIC_API_URL ?? localhost:8787' into getApiBaseUrl()
- under portless, derive the API's sibling origin (web.localhost -> api.localhost),
  carrying the per-worktree prefix and port automatically (works for :443 and :1355)
- explicit NEXT_PUBLIC_API_URL still wins; localhost:8787 fallback for direct runs
…U10)

- docs/portless-dev-workflow.md: host setup, :443-vs-:1355, root-proxy pitfall,
  multi-agent workflow, service discovery, Expo (planned), recovery
- portless:clean script (portless prune) for stale-process recovery
…iew)

- swap the app label generically (<app>.localhost -> api.localhost) instead of
  hardcoding 'web.localhost' — same logic now works for admin/trails when adopted
- document deferred portless API-origin parity for admin/trails/guides (U6 review finding)
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 483d05b0-64e7-4e04-bc1f-cd53402989f2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch explore/portless-packrat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file api labels Jun 1, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
packrat-admin 89eeaa5 Commit Preview URL

Branch Preview URL
Jun 01 2026, 08:39 PM

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for API Unit Tests Coverage (./packages/api)

Status Category Percentage Covered / Total
🔵 Lines 95.12% (🎯 95%) 741 / 779
🔵 Statements 95.12% (🎯 95%) 741 / 779
🔵 Functions 100% (🎯 97%) 46 / 46
🔵 Branches 95.51% (🎯 92%) 298 / 312
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/api/src/utils/cors-origins.ts 100% 100% 100% 100%
Generated in workflow #1553 for commit f63d45f by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Expo Unit Tests Coverage (./apps/expo)

Status Category Percentage Covered / Total
🔵 Lines 97.46% (🎯 95%) 577 / 592
🔵 Statements 97.46% (🎯 95%) 577 / 592
🔵 Functions 100% (🎯 97%) 51 / 51
🔵 Branches 93.65% (🎯 92%) 192 / 205
File CoverageNo changed files found.
Generated in workflow #1553 for commit f63d45f by the Vitest Coverage Report Action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant