Claude Codeをブラウザから使う方法は2026年時点で大きく2つある。①Anthropic公式のClaude Code Remote Control(2026年2月25日リリース)でローカルマシンを遠隔操作する方法、②自社VM上でClaude Codeを動かしWebSocket+PTYでブラウザに接続する方法である。本記事では、後者の本格実装パターンを、renueが自社で運用している「AI Terminal VM Wrapper」の実装知見をもとに解説する。エンジニア以外のメンバーにもClaude Codeを安全に提供したい組織向けの実装ガイドである。
2つのアプローチの違い
| 観点 | Claude Code Remote Control | VM Wrapper方式(自社運用) |
|---|---|---|
| 実行場所 | ローカルマシン(ユーザー個別) | 集中管理されたVM |
| 環境 | ユーザー固有のローカル環境 | 全員統一された環境 |
| セットアップ負荷 | ユーザー個別に必要 | 管理者が一度構築 |
| 非エンジニアの利用 | 難しい(ローカル設定必要) | 容易(URLだけで開始) |
| 権限制御 | CLAUDE_REMOTE_ALLOWED_PATH | VM側で完全制御 |
| コスト | ユーザー個別 | VM集約(共有) |
| データ置き場 | ローカル | VM(チーム共有可能) |
renueの実装では、非エンジニアを含む全社員がClaude Codeを使えることを重視し、VM Wrapper方式を採用している。
本番品質なVM Wrapper実装に必要な5レイヤー
レイヤー1: WebSocket + PTY連携
ブラウザからClaude Codeを操作するには、WebSocketでテキストを双方向にやり取りし、VM側でPTY(擬似ターミナル)経由でClaude Codeプロセスに入出力を渡す必要がある。
主要なPython標準ライブラリ
- pty: 擬似ターミナルの生成
- fcntl: ファイルディスクリプタ制御(ノンブロッキング)
- select: 入出力待機
- termios: ターミナル属性制御
- struct: ウィンドウサイズ送信(TIOCSWINSZ)
- signal: シグナル制御
FastAPIでのWebSocketエンドポイント
@app.websocket("/ws/terminal/{session_id}")
async def terminal_websocket(
websocket: WebSocket,
session_id: str,
api_key: str = Query(...),
):
# APIキー検証
verify_api_key(api_key)
await websocket.accept()
# セッション取得 or 新規作成
session = _terminal_sessions.get(session_id)
if session is None:
session = await _create_new_session(session_id)
else:
# 既存セッションに再接続(後述)
await _reconnect_session(session, websocket)
# 入出力ループ
...
レイヤー2: セッション再接続(最重要)
ブラウザベースのClaude Code運用で最も難しいのは「ネットワーク切断後の再接続」である。ユーザーがタブを閉じた、ネットワークが一時的に途切れた、PCがスリープしたといった場合、サーバー側のClaude Codeプロセスは生きたまま維持する必要がある。そうしないと、長時間の処理がすべて失われてしまう。
renueの実装パターン
- セッションディクショナリ: `_terminal_sessions: dict[str, dict[str, Any]]` でsession_idをキーにセッションを保持
- 出力バッファ: WebSocket切断中のClaude出力を`deque`で保持し、再接続時に一括送信
- タイムアウト制御: 切断後7200秒(2時間)までセッション維持(`AI_TERMINAL_SESSION_TIMEOUT_SECONDS`)
- クリーンアップタスク: 30秒ごとに期限切れセッションを削除(`AI_TERMINAL_SESSION_CLEANUP_INTERVAL_SECONDS`)
- 出力バッファ上限: 切断中の出力を400チャンクまで保持(`AI_TERMINAL_OUTPUT_BUFFER_CHUNKS`)
- Graceful termination: セッション終了時の猶予時間を設定(`AI_TERMINAL_TERMINATION_GRACE_SECONDS`)
再接続ロジックのポイント
再接続時は、以下の手順で状態を復元する。
- 既存セッションの有無をsession_idで確認
- 存在する場合、バッファに溜まった出力を全て送信
- WebSocket参照を新しいコネクションに差し替え
- PTYへの入出力ループを再開
レイヤー3: プロファイル別の権限制御
VM Wrapper方式の強みは「ユーザーの習熟度に応じて権限を変えられる」ことである。renueの実装では以下のプロファイルを定義している。
beginner_chat プロファイル(やさしいモード)
非エンジニア向けのモード。Claude Codeに許可するツールを極限まで制限する。
- 許可される操作: `Bash(renue *)` のみ(renue CLIの実行のみ)
- 禁止コマンド: `cd`, `pushd`, `popd`, `dirs` を含む任意のディレクトリ変更
- ワークスペース分離: ユーザーごとに専用ディレクトリ(`/tmp/ai-terminal/workspaces/{user}/`)を自動作成
- 3層防御: ブラウザ/API/VMの3層で同じ防御を入れて多層防御
これにより、非エンジニアが誤ってファイルシステムを破壊したり、他のユーザーのファイルにアクセスしたりするリスクをゼロにできる。
slack_ops プロファイル(Slack対応モード)
Slack対応botとして動かす場合のプロファイル。読み取り専用の調査・回答タスクに特化。
- 許可ツール: `Bash Read Glob Grep WebFetch WebSearch`
- 行動指針(システムプロンプト): 「Slack向けに簡潔な返答」「renue CLIを優先使用」「不必要なファイル探索を避ける」
- JSON出力: `reply_text`を含む構造化JSON
ecommerce プロファイル
EC運用向け。renue CLIを使った広告運用・クリエイティブ管理に特化。
レイヤー4: PATHとsystemd設定の罠
Claude Codeをsystemdサービスとして動かす場合、PATH設定に罠がある。
典型的な問題
- Anthropic公式のネイティブインストーラー(`claude install`)は`~/.local/bin/claude`にバイナリを配置する
- systemdのデフォルトPATHには`~/.local/bin`が含まれない
- 結果、systemdから起動した際にClaudeバイナリが見つからない
解決策
# claude-wrapper.service の [Service] セクション
Environment="PATH=/home/azureuser/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
デプロイスクリプトで自動的にPATHを追加する仕組みを作っておくと、VM再構築時も安定する。
レイヤー5: デプロイと運用
自動デプロイフロー(GitHub Actions)
renueの実装では、`src/**`または`scripts/**`の変更がmainにpushされると、GitHub ActionsがSSH経由で自動的にVMにデプロイする。
必要なGitHub Secrets
- AI_TERMINAL_VM_SSH_KEY(必須): SSH秘密鍵
- AI_TERMINAL_VM_HOST(任意): VMのホスト
- AI_TERMINAL_VM_USER(任意): SSHユーザー
- AI_TERMINAL_VM_PORT(任意): SSHポート
- AI_TERMINAL_VM_KNOWN_HOSTS(任意): 未設定時は`ssh-keyscan`で自動取得
デプロイスクリプトの処理
- extension.pyをVMに転送
- main.pyのmanaged blockを冪等更新(既存コードとの共存)
- `python3 -m py_compile`で構文チェック
- `sudo systemctl restart claude-wrapper`
ロールバック対策
各デプロイ時に`/tmp/claude-wrapper-backups/`にバックアップを自動保存する。問題発生時はバックアップを復元して`systemctl restart`で即座にロールバックできる。
ローカル開発環境(Docker E2E)
VM本番環境と同じ構成をローカルのDockerで再現できると、開発・テストが楽になる。renueの実装では以下の構成を取っている。
- docker-compose default: 本物のClaude Code CLIをコンテナ内で起動
- 認証状態の永続化: `/claude-home`をnamed volumeで永続化
- SERVICE_API_KEY管理: `./scripts/sync-local-service-api-key.sh`でローカル用のAPIキーを`.local/service_api_key.env`に抽出
- fake claude切替: `AI_TERMINAL_USE_FAKE_CLAUDE=1`でfake transcriptモード(テスト用)に切替
renueの実装事例 — 全社員がClaude Codeを使える環境
renueは「Self-DX First」の方針のもと、社長から事務スタッフまで全員がClaude Codeを日常業務で使える環境を構築している。社内12業務(採用・経理・PMO・評価など)を553のAIツールで自動化済み(2026年1月時点)であり、その中核がAI Terminal VM Wrapperによる全社共通環境である(全て公開情報)。
公開されている特徴
- ブラウザ側(nextjs-sales): Next.jsでターミナルUIを提供
- API中継(pj-shared-fastapi-renue): `/ws/ai-terminal/browser/{session_id}`でブラウザとVM間を中継
- VM受け入れ側(ai-terminal-vm-wrapper): `/ws/terminal/{session_id}`でClaude Codeプロセスを管理
- 3つのプロファイル: beginner_chat / slack_ops / ecommerce
- 非エンジニアも利用可能: 「Bash(renue *)」のみ許可するやさしいモード
- セッション再接続: 切断後2時間までセッション維持
セキュリティベストプラクティス
1. APIキー認証
WebSocketエンドポイントは必ずAPIキー認証で保護する。`?api_key=
2. SERVICE_API_KEYの管理
`SERVICE_API_KEY`はFastAPI側(pj-shared-fastapi-renue)とVM側(ai-terminal-vm-wrapper)で一致させる必要がある。環境変数で渡し、ソースコードには一切記載しない。
3. ユーザー別ワークスペース分離
各ユーザーごとに専用ディレクトリを自動作成し、他ユーザーのファイルにアクセスできないようにする。特にbeginnerモードではcd禁止で絶対的に分離する。
4. 出力バッファの上限
出力バッファを無制限にすると、大量出力時にメモリ枯渇するリスクがある。チャンク数で上限(デフォルト400)を設定する。
5. セッションタイムアウト
切断後のセッション保持は必要だが、無期限保持はリソースを圧迫する。適切なタイムアウト(デフォルト2時間)を設定する。
導入時のよくある失敗パターン
- セッション再接続を実装しない: ネットワーク切断で長時間の処理がすべて失われる
- 権限制御を省略する: 非エンジニアが誤ってシステムを破壊するリスク
- systemd PATHの設定を忘れる: Claude Codeバイナリが見つからず起動失敗
- APIキー認証を後回しにする: 誰でもアクセス可能な状態で脆弱性を抱える
- バッファ上限を設定しない: 大量出力時のメモリ枯渇
- デプロイのロールバック手段がない: 問題発生時に復旧できない
- ローカル開発環境がない: VMでしか動作確認できず開発速度が落ちる
よくある質問
Claude Code Remote ControlとVM Wrapper、どちらを選ぶべき?
個人利用ならClaude Code Remote Control(公式)が最も簡単。組織全体で運用する場合、特に非エンジニアも含めて利用させたい場合はVM Wrapper方式が適している。統一環境と集中管理のメリットが大きい。
セッション再接続は本当に必要?
必要である。Claude Codeの処理は数分〜数十分かかることが多く、ネットワーク切断で全て失われるとユーザー体験が劣悪になる。本番運用では必須機能である。
権限制御はどこまで厳しくすべき?
利用者の習熟度で分ける。エンジニア向けはフル権限、非エンジニア向けは`Bash(renue *)`のみ、Slack bot向けは読み取りのみ、というように段階的に設計する。
VMのスペックはどれくらい必要?
同時セッション数による。1セッションあたり数百MB〜数GBのメモリを消費する(Claude Codeプロセス+PTY+バッファ)。同時10セッションなら最低16GB RAM、20セッションなら32GB以上を推奨。
コストはどれくらい?
VMの運用コスト(月数万円〜)+ Claude Code API利用料(ユーザー数×プラン料金)+ Azure/AWS等のインフラコスト。組織全体で見れば、ユーザー個別にClaude Codeを設定するより安価になるケースが多い。
