跳到主要內容

【2026 最新】Portless 教學:Claude Code/Codex 多 Worktree 怎麼避免撞 Port?

最後更新: ·
Portless 教學封面:Claude Code 與 Codex 多 Worktree 經命名路由分流並以收據驗證

同時讓 Claude Code、Codex 在兩個 Worktree 改前端時,真正危險的通常不是「3000 被占用」,而是瀏覽器開著舊分頁,最後把 A 分支的畫面當成 B 分支驗收。這篇 Portless 教學會把每個開發伺服器換成有名字的 .localhost 網址,並加上一張能核對 hostname、branch、commit 的驗收收據。

先說最重要的限制:Claude Code 建立的命名 Worktree 通常有分支,Portless 可以自動加前綴;Codex 管理的 Worktree 預設卻是 detached HEAD,不會自動得到分支前綴。若你跳過本文的 Codex 分岔設定,兩個 Agent 仍可能搶同一個網址。

先看結論:Portless 解的不是 Port,而是「我正在看哪個 Worktree」

可驗收網址=Worktree 身分 × 專案名稱 × .localhost。Portless 在前面放一層本機 reverse proxy,外面固定使用像 red-ui.myapp.localhost 的網址,裡面再把請求轉到隨機可用 Port。人與瀏覽器 Agent 記住的是分支身分,不必猜 3000、3001、3002 分別屬於誰。

  • 它能解決:Port 撞號、書籤失效、瀏覽器 Agent 點錯分支、不同 hostname 下的 host-only cookie/儲存空間混在一起。
  • 它不能保證:每種 OAuth provider 都接受任意 callback、寬範圍 Domain cookie 一定隔離、detached HEAD 自動有名字,或兩個尾段相同的分支永不撞名。
  • 最小安全原則:每次驗收先比對 hostname、branch、commit,再測功能;不要用 --force 蓋掉另一個仍在工作的 Agent process。

這個做法可補上 Agent Harness 交接裡「Worktree 並不隔離 Port」的缺口,也很適合跟 AGENTS.md 規則門檻一起使用:前者管工作狀態,後者把「不得測錯分支」寫成可執行規則。

Portless 教學第一步:先用低影響模式跑起 Proxy

本文以 2026 年 9 月 9 日查核的 Portless v0.15.6 為基準。這仍是 1.0 前工具,狀態格式與行為可能變動;這個版本要求 Node.js 24 以上。正式預設會啟用 HTTPS/HTTP/2、建立並信任本機 CA、使用 443,且預設同步 hosts file。第一次只想確認價值時,可以先避開憑證與系統 hosts 變更:

npm install --save-dev portless

# macOS / Linux / WSL:低影響試跑
PORTLESS_SYNC_HOSTS=0 npx portless proxy start --no-tls --port 4010

Windows PowerShell 可先設定 $env:PORTLESS_SYNC_HOSTS="0",再執行同一個 npx portless proxy start --no-tls --port 4010。這不是「最完整」模式,而是刻意把變因縮到只有 proxy 與 route:不用先處理憑證信任、443 權限或 hosts file。等兩個 Worktree 都能正確分流,再逐項開回 HTTPS 與系統整合;若問題出現,你才知道是哪一層造成。

這個模式仍只在本機 loopback 提供服務,網址會多出 :4010.localhost 與其子名稱依 RFC 6761 保留給 loopback;Chrome、Firefox、Edge 通常能直接解析。若 Safari 找不到網址,再決定是否執行 portless hosts sync,不要一開始就把權限提升與路由問題混在一起。

確認可用後,把專案的實際 server command 包進 package.json。以下以 Next.js 為例:

{
  "scripts": {
    "dev": "portless run next dev"
  }
}

Portless 會推導專案名稱、配置 4000–4999 之間的可用 Port,並注入 PORTHOSTPORTLESS_URL。本文後續的 npx portless 會解析剛裝好的專案 dependency,不是臨時下載另一版。Vite、Astro 等已知 server command 可由 Portless 補上對應 flag;但 &&、pipe、env prefix 或轉呼叫另一個 npm script 的複合命令可能無法自動注入。遇到這類腳本,先看 官方 command 說明,不要假設有包 portless run 就一定換了 Port。

同時跑兩個 Worktree:讓分支名稱進入網址

先用 Git 建立兩個「有名字的 linked worktree」。Git 會讓每個工作目錄有自己的 HEAD 與 index;完整規則可查 git worktree 官方文件

# 在主專案執行
git worktree add ../myapp-red -b red-ui
git worktree add ../myapp-blue -b blue-ui

# Terminal A
cd ../myapp-red
npm ci
npm run dev

# Terminal B
cd ../myapp-blue
npm ci
npm run dev

在 linked worktree 且目前分支不是 mainmaster 時,Portless 會把分支名稱清理後放在 base app name 前面;因此你應看到類似 red-ui.myapp.localhost:4010blue-ui.myapp.localhost:4010。root/主 checkout 即使切到 feature branch,仍使用沒有分支前綴的 myapp.localhost;自動前綴只適用於 portless run 這種推導模式。

Portless 將兩個 Worktree 的命名 localhost 網址分流到不同隨機內部 Port
外部網址固定承載 Worktree 身分;內部 Port 可以改變。真正的驗收鍵是 hostname 加上 branch/commit 收據。

這裡有一個容易漏掉的碰撞:目前 Portless 只取分支名稱最後一段。feature/authfix/auth 都可能變成 auth.myapp.localhost。團隊應讓最後一段也唯一,例如 feature/auth-redfix/auth-blue;啟動後再用下列命令核對,不要看到網頁能開就算過關。

npx portless list
git branch --show-current
git rev-parse --short HEAD

portless list 回答「網址現在轉到哪個 PID/Port」,Git 兩行回答「這個 Terminal 實際在哪個分支/commit」。三份資訊要一起保存:只有 URL,無法證明 process 不是稍早留下的;只有 commit,無法證明瀏覽器沒有開到另一個 route。這也是本文所說的收據,而不是單純截一張看似正確的 UI。

如果你使用 Claude Code 的 claude --worktree red-ui,它會建立一個新分支與隔離工作目錄;實際分支會帶有 Claude Code 的命名規則,因此請以 git branch --show-current 顯示的名稱推算網址,不要只拿輸入的短名猜測。想比較兩個 Agent 的工作方式,可先讀 Claude Code vs Codex

Codex 的必要分岔:detached HEAD 要自己命名

Codex App 的 managed worktree 預設是 detached HEAD;而 Portless 對 detached HEAD 回傳「沒有 worktree prefix」。所以兩個 Codex chat 若都從同一專案推導出 myapp,第一個 process 會占用 myapp.localhost,第二個會收到 route 已註冊的錯誤。這不是隨機 Port 壞掉,而是網址身分根本沒有分開。

解法 A:先建立分支,再走自動前綴

# 在各自的 Codex worktree 執行,名稱務必唯一
git switch -c codex-auth-a
npm run dev

這個做法最容易留下可合併、可稽核的 Git 身分;也跟 Coding Agent 最小設定強調的「把規則收進版本控制」一致。

解法 B:保留 detached HEAD,但每個 process 顯式給唯一名稱

# Codex worktree A
npx portless run --name codex-auth-a npx next dev

# Codex worktree B
npx portless run --name codex-auth-b npx next dev

此時網址會是 codex-auth-a.localhost:4010codex-auth-b.localhost:4010。若你的 npm run dev 已經包了 Portless,不要再從外層呼叫同一支 wrapped script;改為直接傳入原始 framework server command。更不要加 --force:它的語意是終止既有 route owner 並接管網址,正好可能殺掉另一個 Agent 的驗收環境。

Portless 教學的核心:交付「分支收據」,不是一張截圖

穩定網址只把身分做成可觀察介面,還需要驗收門檻。最簡單的做法是在 dev build 提供 /api/build-info,至少回傳 branch、短 commit、PORTLESS_URL 與 build timestamp。不要放 token、路徑、使用者名稱或環境變數全集。

{
  "branch": "red-ui",
  "commit": "8f31c2a",
  "url": "http://red-ui.myapp.localhost:4010",
  "builtAt": "2026-09-09T08:30:00Z"
}

接著把瀏覽器 Agent 的驗收順序固定成四步:

  1. 只能開任務指定的完整 URL,不准自行搜尋「哪個 localhost 能開」。
  2. 記錄 location.hostname,確認它包含預期的唯一工作名稱。
  3. 讀取 /api/build-info,把 branch/commit 與該 Worktree 的 git rev-parse --short HEAD 比對。
  4. 三者一致後才測 UI、console、network、HMR,並把 URL+收據 JSON 放進驗收回報。

你甚至可以把任務提示固定成:「先開指定 URL;若 hostname、branch、commit 任一不符,立即停止並回報,不得改測其他 localhost。」這句話的重要性在「停止」:沒有 fail-closed,Agent 可能為了完成任務,自動挑一個能開的頁面繼續測,最後留下完整但屬於錯分支的 console、network 與截圖證據。

這與 AI Coding Agent 修改忠實度的精神相同:證據要能回答「哪一份輸入產生哪一份輸出」。如果你用 Claude Code 的 browser integration,它能直接操作 localhost、檢查 DOM 與 console;Codex 或其他 browser agent 也應套用同一張收據,而不是信任分頁標題或肉眼相似的畫面。

Cookie、OAuth、HMR:三個最容易誤判的測試

1. Cookie 隔離看 hostname 與 Domain,不看 Port

Cookie 的 Domain 規則以 host 範圍決定是否送出;省略 Domain 時,cookie 原則上只回到原始 host。因此 red-ui.myapp.localhostblue-ui.myapp.localhost 比單純換 3000/3001 更容易隔開 host-only session。若應用刻意設定廣域 Domain,子網域仍可能共享 cookie,不能把 Portless 當成安全邊界。

2. OAuth callback 要逐字對上已註冊 URI

OAuth 2.0 規範要求完整 redirect URI 註冊時做 simple string comparison。分支網址每天變動,就可能每個 callback 都要註冊;而 provider 是否接受 .localhost、多個 callback 或 wildcard,必須查該 provider 當下規則。實務上可選一個固定的 auth-dev hostname,或使用自己擁有的 dev.example.com 類網域並明確註冊完整 callback。不要臨時把正式 callback 指向任意 Worktree。

3. HMR 支援不等於你的設定一定正確

Portless 官方說明其 proxy 支援 HTTP/1.1 Upgrade 與 HTTP/2 extended CONNECT,所以 Next.js、Vite 等 HMR WebSocket 可以穿過代理。驗收時仍要實際改一段可見文字,確認目標 hostname 的頁面更新,並檢查 console/network 沒有連回舊的硬編碼 WebSocket URL。若失敗,依序看 portless list、framework 是否真的吃到注入 Port、以及 client 是否快取舊 origin。

診斷時不要同時重啟所有東西。若「網址不存在」,先查 proxy 與 DNS;若「網址存在但回錯分支」,查 route owner 與 receipt;若「頁面可開但 HMR 不動」,才查 WebSocket origin;若「登入後跳錯頁」,比對應用產生的 callback 與 provider 登記值。每次只換一個變因,才能避免把 DNS、路由、應用與第三方登入四種問題誤判成同一個 Portless bug。

安全收尾與故障診斷:讓下一個 Agent 不接到幽靈 Route

預設本機模式只綁 127.0.0.1::1。除非真的要拿手機測試,否則不要開 --lan;LAN 模式會讓同網路裝置可存取服務,威脅面完全不同。每次平行任務結束後執行:

# 先在各 dev server 按 Ctrl-C
npx portless list
npx portless doctor

# 只在 crash 留下 orphan process 時使用
npx portless prune

# 不再需要共享 proxy 時再停止 daemon
npx portless proxy stop

# 臨時需要繞過 proxy,回到 framework 原生 Port
PORTLESS=0 npm run dev

Dev server 正常退出時會移除自己的 route,但 proxy daemon 會繼續存在,直到 proxy stopdoctor 是唯讀健康檢查;prune 會終止被判定為 orphan 的 dev server,應先看清楚輸出。若要完全移除 Portless 建立的 state、CA trust、hosts block 與 service,才使用 portless clean;它可能需要系統權限,且不是日常 teardown 指令。這類 recovery 規則也可加入 Coding Agent 事故復原演練

什麼情況不值得導入 Portless?

  • 你永遠只跑一個 server,也沒有 cookie、OAuth、browser agent 或書籤需求:固定 3000 最簡單。
  • 工具鏈固定依賴數字 Port,且無法讀 PORT 或接受 --port:先改啟動腳本,否則 proxy 只是再加一層故障點。
  • 測試需要容器、手機或遠端協作:loopback named URL 不等於跨裝置網路方案,應另外評估容器網路、LAN 或 tunnel 的權限與暴露。
  • 兩個 Worktree 還共用資料庫、queue、cache、背景 worker、上傳目錄或測試帳號:Portless 只隔離路由,這些資源仍要另外命名與清理。
  • 團隊無法約束分支尾段唯一、Codex detached 命名與 receipt gate:網址看起來漂亮,但仍可能驗錯。

若要正式導入,先用兩個非關鍵分支做一週試行,並在 PR 範本新增四個欄位:完整驗收 URL、Worktree 分支、短 commit、portless list 對應。再記錄 DNS、憑證、HMR、登入與 teardown 是否各自通過。只要其中一項需要人工猜測,就先修正腳本或規則,不要把不確定性留給下一位 Agent。若無法穩定產生收據,先保留原生 Port 流程作為明確 fallback,並指定維護者定期查核版本、憑證與清理程序,避免無人負責的系統漂移。這份小型檢核比「大家以後都用 named URL」的口頭約定更能防止回歸。

Portless 的價值不是省下輸入四位數 Port 的時間,而是把「這個瀏覽器頁面屬於哪個工作單元」變成可檢查、可留存、可阻擋的介面。若你正在建立更完整的協作框架,可接著看 AI Agent Harness 是什麼如何建立 Agent Harness;想系統化練習則可前往 AlphaLab 課程,更多工具整理在 AI 專區

Portless 教學常見問題

Portless 會取代 Next.js 或 Vite 的 dev server 嗎?

不會。它是在前面加本機 proxy,後面的 framework dev server 仍照常執行,只是改用 Portless 配置的內部 Port。

一定要用 HTTPS 嗎?

不一定。正式預設是 HTTPS/HTTP/2,但首次評估可用 --no-tls 與非特權 Port。需要 Secure cookie、真實 TLS 行為或 OAuth 時,再採用受信任的本機 CA 流程。

為什麼 Claude Code 可以自動分流,Codex 卻撞名?

關鍵不是 Agent 品牌,而是 Git 狀態。Portless 需要 linked worktree 加上非預設的命名分支;Codex managed worktree 預設 detached HEAD,所以要先建分支或顯式指定唯一 --name

分支有斜線時,網址會長什麼樣?

目前版本只使用最後一段並清理成 hostname label。feature/auth-red 會取 auth-red;因此不同路徑若有相同尾段仍可能衝突。

看到 route already registered 可以直接加 –force 嗎?

不建議。--force 會接管 route,可能終止另一個 Agent 正在使用的 process。先用 portless list 找 owner,再修正分支尾段或改成唯一名稱。

不同 Port 本來就能隔離 cookie,為何還需要 hostname?

Cookie 的主要傳送範圍由 host、Domain、Path、Secure 等屬性決定,Port 不是 cookie scope。不同 hostname 搭配 host-only cookie 才有清楚的隔離;廣域 Domain 仍會破壞隔離。

OAuth callback 可以用 wildcard 一次涵蓋所有 Worktree 嗎?

不能一概而論。OAuth 規範與各 provider 實作要求不同,完整 URI 常需精確比對。以 provider 當下官方規則為準;不支援時使用固定 auth-dev hostname 或逐一註冊。

Portless 關掉後怎麼回到原生 Port?

可用 PORTLESS=0 npm run dev 暫時繞過 proxy。若要移除所有 state、CA 與 hosts 項目,再閱讀官方說明後使用 portless clean

接著閱讀

左右滑動查看更多推薦

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週最多三封:一封 Weekly 週報與最多兩封關鍵 Alpha Signal。

我們不會 spam,隨時可退訂。