internal-dev-workbench について
internal-dev-workbenchは、Workflow SDKの開発者向けのオプトイン型ツールです。3ペインのtmuxセッションを作成し、worktreeごとのportless URLとClaude Codeのステータスラインスクリプトを提供します。
主な機能
- 013ペインのtmux開発セッション
- 02worktreeごとのportlessルート
- 03現在のworkbenchの可観測性UI
- 04Claude Codeステータスライン用スクリプト
- 05E2Eテストと再起動の手順
活用シーン
- 01開発サーバーと可観測性UIの並行起動
- 02git worktree間のポート競合の回避
- 03サーバーURLの確認とtmuxセッションへの接続
- 04ローカルセッションに対するE2Eテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel/ workflow --skill internal-dev-workbenchインストール後、Claude Code で/internal-dev-workbenchを入力して読み込まれたか確認します。
同梱ファイルstatusline.sh
詳細
internal-dev-workbench
エンドツーエンドの Workflow SDK 開発向けに、独自の設定を施した 3 ペインの tmux セッションを起動します。各ペインは portless 経由で起動されるため、URL が安定し、ワークツリーごとに分離されます(例:https://<branch>.turbopack.localhost)。これにより、ポートの競合を起こさずに複数のワークツリーを同時に実行できます。付属のステータスラインスクリプトは、アクティブな URL を Claude Code のプロンプトに表示します。
これはオプトインのコントリビューター向けツールです。リポジトリ標準の開発手順(portless を使わず、ワークベンチから pnpm dev を実行)は影響を受けません。
前提条件
tmuxがインストールされていることportlessがグローバルにインストールされていること(npm i -g portlessまたは Homebrew 経由)。portless --versionで確認してください。- リポジトリのセットアップが完了していること:
pnpm install && pnpm build。新しいワークツリーで初めて実行する場合は、開発サーバーを起動する前に両方を完了する必要があります(ワークベンチアプリはビルド済みのワークスペースパッケージに依存しており、pnpm buildを実行しないとworkflowに対するMODULE_NOT_FOUNDが発生します)。 - e2e テストを実行する際は、開発サーバーに
WORKFLOW_PUBLIC_MANIFEST=1が必要です(設定しないと/.well-known/workflow/v1/manifest.jsonへのアクセスが制限されます)。
レイアウト
main-vertical では、開発サーバーが左の列を占め、右の列には上部にオブザーバビリティ UI、下部にスクラッチパッド用のシェルが配置されます。
+----------------------+--------------------------+
| | PANE_OBS: workflow web |
| | (observability UI |
| PANE_DEV: turbopack | scoped to the |
| (Next.js dev) | workbench app) |
| +--------------------------+
| | PANE_SH: zsh scratchpad |
| | (repo root, for build, |
| | tests, e2e, git, etc.) |
+----------------------+--------------------------+
セットアップ
セッション名は、ワークツリーの portless プレフィックス(現在のブランチ名のベース名)と必ず一致させてください。これにより、ステータスラインや、ブランチ名からプレフィックスを取得するその他のツールがセッションを見つけられます。必ず最初に tmux ls を実行し、その名前の既存セッションがないことを確認してください。既存のセッションは決して終了しないでください。
tmux のペインインデックスは pane-base-index によって異なります(デフォルトは 0、一般的なドットファイルでの上書き値は 1)。どちらの設定でも正しく動作するよう、分割時に -P -F '#{pane_id}' で各ペインの ID を取得し、その ID をターゲットとして使います。
REPO=/path/to/workflow--<worktree-suffix>
# Session name = basename of the branch (matches portless's subdomain prefix
# and the statusline's `tmux attach -t <prefix>` indicator). For branch
# `pgp/foo-bar` this resolves to `foo-bar`.
SESSION=$(git -C "$REPO" rev-parse --abbrev-ref HEAD)
SESSION="${SESSION##*/}"
# Create the session and capture the initial pane ID
PANE_DEV=$(tmux new-session -d -s "$SESSION" -c "$REPO" -P -F '#{pane_id}')
PANE_OBS=$(tmux split-window -h -t "$PANE_DEV" -c "$REPO" -P -F '#{pane_id}')
PANE_SH=$(tmux split-window -v -t "$PANE_OBS" -c "$REPO" -P -F '#{pane_id}')
tmux select-layout -t "$SESSION" main-vertical
# Pane DEV (left): Next.js turbopack workbench, with manifest exposed for e2e
tmux send-keys -t "$PANE_DEV" \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m
# Pane OBS (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$PANE_OBS" \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m
# Pane SH (bottom-right): scratchpad at repo root
tmux send-keys -t "$PANE_SH" 'echo "scratchpad: $(pwd)"' C-m
tmux attach -t "$SESSION"
両方のサーバーが準備できたら、portless list でルートを確認できます。portless run を使うと、リンクされた各ワークツリーにブランチ名をプレフィックスとする一意のサブドメイン(例:stepflow-test.turbopack.localhost)が割り当てられるため、設定を変更せずに複数のワークツリーを共存させられます。
各要素の理由
portless run --name <name>(portless <name> <cmd>の代わり):runは Git ワークツリーを自動検出し、サニタイズしたブランチ名をサブドメインとして先頭に付加します。--nameフラグを指定すると、ワークツリーのプレフィックスはそのままに、推定されたベース名を上書きできます。pnpm workflow web --webPort $PORT --noBrowser(packages/webでpnpm devを実行する代わり):バンドルされた CLI は、現在のワークベンチアプリに接続するよう設定されたオブザーバビリティ UI を起動し、そのプロジェクトのローカル World データを読み込みます。packages/web独自のdevスクリプトを実行すると UI は起動しますが、何にも接続されません。sh -c '... --webPort $PORT':portless が--portを自動挿入するのは、コマンドラインから認識できる既知のフレームワークを検出した場合だけです。コマンドが CLI ラッパー(pnpm workflow web)の場合は、sh -cでラップし、portless が常に設定する$PORTを明示的に読み取ってください。- 開発ペインの
WORKFLOW_PUBLIC_MANIFEST=1:e2e テストで開発サーバーからワークフローレジストリを取得するために必要です。 -P -F '#{pane_id}':ユーザーのpane-base-indexの設定に関係なく、このスニペットが正しく動作するようにします(デフォルト値は設定によって異なります)。
Claude ステータスラインとの連携
このスキルには、現在のブランチから worktree のプレフィックスを取得して、簡潔な行を出力するステータスライン用ヘルパー skills/internal-dev-workbench/statusline.sh が含まれています。
dev · obs · tmux attach -t <worktree-prefix>
dev/obs のラベル(Nerd Font のロケット/グラフのグリフ)は OSC 8 ハイパーリンクで、iTerm2、Kitty、WezTerm、Terminal.app、Ghostty でクリックできます。太字、下線、明るいシアンのスタイルで、リンクであることを示します。tmux のフラグメント(Nerd Font のコピー用グリフ)は太字の明るい緑で、「クリック」ではなく「これをコピー」という意味を示します。worktree のプレフィックスと完全に一致する名前のセッションが存在する場合にのみ表示され、すぐに貼り付けられる tmux attach -t <prefix> の呼び出しがそのまま出力されます。アイコンを正しく表示するには、フォントに Nerd Font のグリフが含まれている必要があります。含まれていない場合は代替文字のボックスが表示されますが、レイアウトはそのまま機能します。各要素は独立しています。portless に <prefix>.turbopack.localhost のルートがない場合は、dev のフラグメントが省略されます。ほかの要素も同様です。表示するものがない場合、スクリプトは何も出力せず、ステータスラインも表示されません。
すべてのセッションと worktree で機能するよう、~/.claude/settings.json に設定します。パスは worktree ではなく、メインのチェックアウトを指定してください。worktree は削除されるため、~/github/vercel/workflow--<branch>/... のようなパスは、その worktree を削除すると使えなくなります。
{
"statusLine": {
"type": "command",
"command": "$HOME/github/vercel/workflow/skills/internal-dev-workbench/statusline.sh"
}
}
メインのチェックアウトが別の場所にある場合は、プレフィックスを調整してください。スクリプト自体は worktree に対応しています。標準入力から Claude の workspace.current_dir を読み込み、現在のブランチを取得するため、~/github/vercel/workflow/... から同じスクリプトを呼び出しても、Claude セッションが実行されている worktree のルートが正しく表示されます。
出力ルール:
- 表示するものがない(一致する portless のルートも、tmux セッションもない)→ 出力なし。
- 各要素は個別に表示されます。サーバーを起動しても tmux セッションがない場合は、dev/obs のフラグメントのみが表示されます。逆の場合は、tmux のフラグメントのみが表示されます。
- Git のコンテキストがなくてもルートがある場合 → 最初に一致する
turbopack/workflow-obsのルートにフォールバックし、tmux インジケーターは表示されません。
すでにステータスラインを使用していて、internal-dev-workbench の情報を追加したい場合は、command をそのまま置き換えるのではなく、既存のラッパースクリプト内でヘルパーを実行し、その出力を連結してください。
workflow ファイルの編集後に再起動する
workflow マニフェストは dev-server の起動時にビルドされます。workbench/example/workflows/*.ts に追加した新しい workflow やステップ(および他の workbench にあるそれらのシンボリックリンク)は、HMR を使っても、dev サーバーを再起動するまでは実行時に反映されません。
tmux send-keys -t "$PANE_DEV" C-c
# Wait for the prompt to return
tmux send-keys -t "$PANE_DEV" \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m
新しい workflow が登録されていることを確認します(portless list から割り当てられたローカルポートを使うか、信頼済み CA と .localhost の URL を使います)。
/usr/bin/curl -s "$(portless get turbopack)/.well-known/workflow/v1/manifest.json" \
| grep -o '<your-new-workflow>'
portless が管理する子プロセスの外部で HTTPS URL にアクセスする Node クライアントには、NODE_EXTRA_CA_CERTS=/tmp/portless/ca.pem が必要です。portless trust の後は、ブラウザーで問題なくアクセスできます。
このセッションに対して e2e テストを実行する
スクラッチパッドペインから実行します。テストランナーで TLS を回避するには、portless に割り当てられたローカルポートを使います。
PORT=$(portless list | awk '/turbopack/ {n=split($3,a,":"); print a[n]; exit}')
DEPLOYMENT_URL="http://localhost:$PORT" APP_NAME="nextjs-turbopack" \
pnpm vitest run packages/core/e2e/e2e.test.ts -t "<test name>"
または、CA を信頼したうえで portless の URL を使います。
NODE_EXTRA_CA_CERTS=/tmp/portless/ca.pem \
DEPLOYMENT_URL="$(portless get turbopack)" APP_NAME="nextjs-turbopack" \
pnpm vitest run packages/core/e2e/e2e.test.ts -t "<test name>"
終了処理
tmux kill-session -t "$SESSION"
portless は各子プロセスの終了時にルートを削除します(portless list をきれいな状態にしたい場合は、先に各ペインで Ctrl+C を押してください)。プロキシ自体はほかのセッションのために稼働し続けます。必要に応じて、portless proxy stop で明示的に停止してください。
トラブルシューティング
- 開発ペインでの
MODULE_NOT_FOUND: 'workflow':ワークスペースのパッケージがビルドされていません。リポジトリのルートでpnpm buildを実行し、ペインを再起動してください。 - 可観測性 UI に実行が表示されない:obs ペインが
workbench/nextjs-turbopack(または調査したい任意のワークベンチ)内から起動されていることを確認してください。CLI は現在の作業ディレクトリにあるローカルの World を読み込みます。 - react-router が portless のポートではなく
:5173で起動する:obs ペインでpackages/webからpnpm devを実行すると発生します。上記のpnpm workflow web --webPort $PORT形式に切り替えてください。 - 起動時のソースマップに関する警告(
failed to read input source map ... packages/serde/dist/index.js.map):この警告は無害で、開発を妨げることはありません。 99_e2e.tsの編集後にワークフロー登録が古いままになる:開発ペインを再起動してください。HMR ではマニフェストは再ビルドされません。- ステータスラインに何も表示されない:
portless listに一致するルートが少なくとも 1 つあること、settings.jsonのパスが絶対パスであること、スクリプトが実行可能であること(chmod +x)を確認してください。