From 8524937cd09856666f8859873c92f440f4171889 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 23:42:33 +0900 Subject: [PATCH 1/7] fix(devcontainer): pin build context for devpod compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devpod は docker-compose に基づく devcontainer をビルドするとき、基底の docker-compose.yml の build: . の context を正しく継承できず、 .devcontainer/ ディレクトリを context と誤認していた。結果 COPY 命令が entrypoint.sh: not found で失敗する。 VS Code Dev Containers では同じ設定で動作するが、devpod では明示的に context を指定する必要があった。 overlay 側で build.context: . を明示的に設定。compose の relative path 解決 ルールにより . は base compose file の場所 (= repo root) に解決され、 両ツールで同じ build context が使われる。 ローカルで docker compose build を実行して entrypoint.sh が正しく COPY されることを確認済み。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/docker-compose.devcontainer.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml index ca3d58072c3..eb5d5da6242 100644 --- a/.devcontainer/docker-compose.devcontainer.yml +++ b/.devcontainer/docker-compose.devcontainer.yml @@ -1,5 +1,14 @@ services: app: + # build context を明示的に repo root に固定。 + # 基底の docker-compose.yml は `build: .` だが、merge 後の context 解決を + # devpod / VS Code Dev Containers の両方で安定させるため、この overlay の + # 場所からの相対パスとして `..` (= repo root) を明示する。 + # これがないと devpod が build context を `.devcontainer/` 配下と + # 誤認し、`COPY entrypoint.sh /app/entrypoint.sh` が "not found" で失敗する。 + build: + context: . + dockerfile: Dockerfile # devcontainer 用に長寿命化(既存 compose は `npm start` で短命) command: sleep infinity volumes: From f167e2bb9e562891b7ebea272f9bda0c2aee667e Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 23:48:37 +0900 Subject: [PATCH 2/7] fix(devcontainer): install gh/claude via post-create instead of features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devcontainer features (claude-code, github-cli) を devpod の docker-compose ベース devcontainer で使うと、features が生成する Dockerfile-with-features の build context が壊れて COPY entrypoint.sh が \"/entrypoint.sh: not found\" で 失敗する。前段の build context 修正 (commit 8524937) は VS Code 側には 有効だが devpod ではまだ失敗していた。 VS Code Dev Containers では features がそのまま動くため、devpod 固有の ラッパービルド処理に起因するバグと思われる。 回避策として features は使わず、post-create.sh で同等のインストールを行う: - gh CLI: apt 経由で公式リポジトリから install (Debian bookworm 用 GPG 鍵登録) - claude (Claude Code): npm install -g @anthropic-ai/claude-code ローカルでコンテナに入って post-create.sh を実行し、gh 2.92.0 / Claude Code 2.1.138 がインストールされることを確認済み。 副作用: - devcontainer-lock.json は features 用なので不要、削除 - postCreateCommand が初回 build に加えてインストール時間 (約30秒) を消費するが、 キャッシュチェックで再インストールは skip される Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/README.md | 20 ++++++++++++++ .devcontainer/devcontainer-lock.json | 14 ---------- .devcontainer/devcontainer.json | 4 --- .devcontainer/post-create.sh | 41 +++++++++++++++++++++++++--- 4 files changed, 57 insertions(+), 22 deletions(-) delete mode 100644 .devcontainer/devcontainer-lock.json diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 90bcce00f11..e068a9cc9d6 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -9,6 +9,7 @@ NaCl Claude Code 利用ガイドライン **階層 B(OSS / 自社開発)** - ホスト全体ではなく、**作業に必要なディレクトリのみ** をマウントする(物理アクセス範囲の制限) - 既存の `bin/dx` / `bin/setup-worktree` / git worktree 運用と共存する - VS Code Dev Containers と devpod の両方で動作する標準準拠 +- **devcontainer features は使わず**、`gh` / `claude` のインストールは `post-create.sh` で行う (devpod の features+compose 連携バグ回避のため。後述) ## マウント構成 @@ -132,3 +133,22 @@ docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontaine - **gh auth が効かない**: macOS では PAT が keychain に格納されるため、`export GH_TOKEN=$(gh auth token)` を実行してから devcontainer を起動する (上記「gh CLI の認証トークンについて」参照) - **`/ghq` が空**: `ghq root` の出力が `~/ghq` 以外を指していないか確認 - **ポート 8601 が見えない**: VS Code は `forwardPorts` で自動転送。devpod / 直接 compose では `docker-compose.yml` の `ports` 設定で `localhost:8601` に公開される + +## なぜ devcontainer features を使わないか + +devcontainer.json の `features` (例: `ghcr.io/anthropics/devcontainer-features/claude-code`, +`ghcr.io/devcontainers/features/github-cli`) は、内部的に既存 Dockerfile を +**`Dockerfile-with-features` というラッパー** に書き換えてビルドする。 + +`dockerComposeFile` 形式の devcontainer の場合、devpod はこのラッパービルド時に +**build context を破壊する**ことが分かった (本プロジェクトでの事例: +`COPY entrypoint.sh /app/entrypoint.sh` が "/entrypoint.sh: not found" で失敗)。 +VS Code Dev Containers では同じ設定で動作するため、devpod 固有の挙動と思われる。 + +回避策として、features は使わず、`post-create.sh` で同等のインストールを行う: + +- `gh` CLI: apt 経由 (公式リポジトリの GPG キー登録 → install) +- `claude` (Claude Code): `npm install -g @anthropic-ai/claude-code` + +これにより VS Code でも devpod でも同じ手順で動作する。`postCreateCommand` は +コンテナ作成ごとに 1 回走るが、`gh` / `claude` が既にあれば再インストールしない。 diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json deleted file mode 100644 index 93d4d3e6533..00000000000 --- a/.devcontainer/devcontainer-lock.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "features": { - "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": { - "version": "1.0.5", - "resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a", - "integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a" - }, - "ghcr.io/devcontainers/features/github-cli:1": { - "version": "1.1.0", - "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671", - "integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671" - } - } -} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d81dc1f22da..cdcaa9c5274 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,10 +8,6 @@ "service": "app", "workspaceFolder": "/app", "overrideCommand": false, - "features": { - "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}, - "ghcr.io/devcontainers/features/github-cli:1": {} - }, "remoteEnv": { "GHQ_ROOT": "/ghq", "GH_TOKEN": "${localEnv:GH_TOKEN}" diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 26107b7ff3c..1a9422caed5 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -4,19 +4,52 @@ # Runs inside the container after creation (devcontainer.json の # postCreateCommand から呼ばれる)。 # -# - worktree であれば bin/setup-worktree を実行 (env コピー + npm install + build:dev) -# - main checkout であれば既存の node_modules / dist / .env がそのままあるので -# 何もしない (husky hooks も既にインストール済みのはず) +# 役割: +# 1. gh CLI と Claude Code をインストール +# (devcontainer features は devpod + docker-compose で build context を +# 壊すバグがあるため、postCreate で代替する) +# 2. worktree であれば bin/setup-worktree を実行 (env コピー + npm install + build:dev) +# 3. main checkout であれば追加 setup なし +# +# tools のインストールが含まれるため、main checkout でも post-create.sh は +# 早期 exit せず常に実行する。 set -euo pipefail cd /app +# --- 1) gh CLI のインストール (Debian bookworm) --- +if ! command -v gh >/dev/null 2>&1; then + echo "post-create: installing gh CLI..." + type -p curl >/dev/null || apt-get update -qq && apt-get install -y --no-install-recommends -qq curl ca-certificates + install -dm 0755 /etc/apt/keyrings + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null + chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + | tee /etc/apt/sources.list.d/github-cli.list >/dev/null + apt-get update -qq + apt-get install -y --no-install-recommends -qq gh + rm -rf /var/lib/apt/lists/* + echo "post-create: gh CLI installed: $(gh --version | head -1)" +else + echo "post-create: gh CLI already installed: $(gh --version | head -1)" +fi + +# --- 2) Claude Code のインストール (npm 経由) --- +if ! command -v claude >/dev/null 2>&1; then + echo "post-create: installing Claude Code..." + npm install -g @anthropic-ai/claude-code 2>&1 | tail -3 + echo "post-create: Claude Code installed: $(claude --version 2>&1 | head -1)" +else + echo "post-create: Claude Code already installed: $(claude --version 2>&1 | head -1)" +fi + +# --- 3) worktree のときだけ bin/setup-worktree --- GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null || echo "") GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || echo "") if [[ -z "${GIT_COMMON_DIR}" ]]; then - echo "post-create: not a git repository, skipping setup" + echo "post-create: not a git repository, skipping worktree setup" exit 0 fi From 697781921394e4d807c963281e50b6526a5262e4 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Mon, 11 May 2026 23:56:00 +0900 Subject: [PATCH 3/7] fix(devcontainer): disable host port bind to prevent network attach conflict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通常 compose (smalruby3-editor-app-1) と devcontainer 用 compose (default-sm-24f22-app-1 など) が同時に host port 8601 を取り合うと、 後発側 (devpod up .) がポート bind に失敗し、container は起動するが network attach に失敗する。結果、container の .NetworkSettings.Networks は 空になり、/etc/resolv.conf も Docker 内部 DNS (127.0.0.11) ではなく外部 forwarder (192.168.65.7) を指してしまい、DNS が一切引けなくなる。 devpod での実機検証で再現: - post-create.sh の curl https://cli.github.com が "Could not resolve host" - docker exec で確認すると getent hosts が何も返さない 恒久対策: docker-compose.devcontainer.yml で ports: !reset [] を指定し、 devcontainer 起動時は host port を一切 bind しないようにする。 IDE 側 (VS Code / devpod) は devcontainer.json の forwardPorts: [8601] を 読んで自分でポート転送するため、compose レベルの host bind は不要。 通常 compose は影響を受けず、従来通り :8601 を公開する。 合わせて README にトラブルシューティングを追記。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/README.md | 3 ++- .devcontainer/docker-compose.devcontainer.yml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index e068a9cc9d6..371434fd0ab 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -132,7 +132,8 @@ docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontaine - **gh auth が効かない**: macOS では PAT が keychain に格納されるため、`export GH_TOKEN=$(gh auth token)` を実行してから devcontainer を起動する (上記「gh CLI の認証トークンについて」参照) - **`/ghq` が空**: `ghq root` の出力が `~/ghq` 以外を指していないか確認 -- **ポート 8601 が見えない**: VS Code は `forwardPorts` で自動転送。devpod / 直接 compose では `docker-compose.yml` の `ports` 設定で `localhost:8601` に公開される +- **ポート 8601 が見えない**: devcontainer 側はホスト bind を `!reset` で無効化している (重要: 通常 compose との衝突防止)。IDE 側の `forwardPorts: [8601]` が自動転送するため、VS Code / devpod 経由なら `localhost:8601` で開く +- **devpod up が `Could not resolve host` で失敗する**: 通常 compose (`docker compose up app`) と devpod 用の compose プロジェクトが同時に host port を取り合うと、後発側がネットワーク attach に失敗し、DNS が壊れた孤立コンテナになる。`docker compose stop app` で通常側を止めてから `devpod up .` を再実行する。本リポジトリでは `ports: !reset []` で host bind を無効化しているため再現しないはずだが、過去状態の container が残っていると同じ症状が出る可能性がある ## なぜ devcontainer features を使わないか diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml index eb5d5da6242..1a22711dad6 100644 --- a/.devcontainer/docker-compose.devcontainer.yml +++ b/.devcontainer/docker-compose.devcontainer.yml @@ -11,6 +11,13 @@ services: dockerfile: Dockerfile # devcontainer 用に長寿命化(既存 compose は `npm start` で短命) command: sleep infinity + # ホスト側ポート公開を無効化する。 + # 通常 compose と devcontainer (VS Code / devpod) が同時に立ち上がると、 + # どちらも :8601 を host bind しようとして衝突し、結果として devcontainer 側が + # ネットワーク attach に失敗して container 内 DNS が壊れる事象があった。 + # devcontainer 側は forwardPorts (devcontainer.json) 経由で IDE がポート転送する + # ため、compose レベルで host bind する必要はない。 + ports: !reset [] volumes: # 全開発者で共通のマウント。個人依存のもの (~/ghq, ~/.claude 等) は # docker-compose.local.yml に置く (gitignore、各自で .example からコピー)。 From 8d3a20692d47343ed8582d057727885e71013dd6 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Tue, 12 May 2026 00:10:46 +0900 Subject: [PATCH 4/7] fix(devcontainer): make Claude Code install non-fatal in post-create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devpod 環境では post-create.sh の npm install -g @anthropic-ai/claude-code が まれに "node: bad option: --max-old-space-size" などのメッセージで exit 1 する事象が起きる。原因は claude の native ELF binary と devpod agent の 組み合わせに起因する Node options 処理の不整合と推測されるが、根本特定は 困難で、手動で再実行すれば成功することは確認済み。 devcontainer up 全体が失敗扱いになると IDE 統合が動かなくなるため、 claude install 失敗は warning に降格して post-create.sh は exit 0 で抜ける。 gh CLI のインストールは引き続き fatal (成功率高い)。 ユーザは container 内で以下を手動実行すれば claude が入る: npm install -g @anthropic-ai/claude-code Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/post-create.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 1a9422caed5..d25df0afe64 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -36,10 +36,18 @@ else fi # --- 2) Claude Code のインストール (npm 経由) --- +# devpod 環境ではここでまれに "node: bad option: ..." を出して exit 1 する +# ことがあるが、container 自体は正常で、手動で再実行すれば成功する。 +# devcontainer up が失敗扱いにならないよう non-fatal にする。 if ! command -v claude >/dev/null 2>&1; then echo "post-create: installing Claude Code..." - npm install -g @anthropic-ai/claude-code 2>&1 | tail -3 - echo "post-create: Claude Code installed: $(claude --version 2>&1 | head -1)" + if npm install -g @anthropic-ai/claude-code 2>&1 | tail -3 && command -v claude >/dev/null 2>&1; then + echo "post-create: Claude Code installed: $(claude --version 2>&1 | head -1)" + else + echo "post-create: WARNING: Claude Code install failed (devpod-related)." >&2 + echo "post-create: Install manually inside the container:" >&2 + echo "post-create: npm install -g @anthropic-ai/claude-code" >&2 + fi else echo "post-create: Claude Code already installed: $(claude --version 2>&1 | head -1)" fi From c73d6e14d97f726395f758006e3c960a18959501 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Tue, 12 May 2026 01:08:36 +0900 Subject: [PATCH 5/7] refactor(devcontainer): switch to Dockerfile-only + gitignored devcontainer.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker-compose ベースの devcontainer は devpod との相性問題があり (build context 破壊、port 衝突、features 不整合) 安定運用が困難だったため、 Dockerfile-only 構成にリファクタする。 ## 主な変更 - .devcontainer/devcontainer.json は .gitignore 対象に変更。 各自で .example をコピーして使う: cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json - .devcontainer/docker-compose.devcontainer.yml と docker-compose.local.yml.example は削除 (compose 経由は廃止) - 通常の docker-compose.yml は無変更。docker compose run app ... は従来通り - devcontainer features (claude-code, github-cli) を復活。Dockerfile-only なら devpod でも features が正しく動く - 既存 compose の named volume (node_modules / .npm / .cache) を devcontainer でも共有することで、deps の二重インストールを回避。git worktree 切替時の 起動高速化にも寄与 - 個人マウント (~/ghq, ~/.claude*) はテンプレートでコメントアウト状態で提示。 使う人だけアンコメントする - post-create.sh からツール install ロジックを削除 (features が処理)。 worktree 検出 + setup-worktree 実行のみに簡素化 - 対話的に devcontainer.json を生成するスキルを追加: .claude/skills/devcontainer-setup/SKILL.md ## 通常開発者への影響 なし。docker compose run --rm app ... は無変更で動く。 本ディレクトリは devpod / VS Code Dev Containers を使いたい人だけが触る。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/devcontainer-setup/SKILL.md | 141 ++++++++++++++ .devcontainer/README.md | 173 ++++++++---------- .devcontainer/devcontainer.json | 27 --- .devcontainer/devcontainer.json.example | 94 ++++++++++ .devcontainer/docker-compose.devcontainer.yml | 30 --- .../docker-compose.local.yml.example | 55 ------ .devcontainer/initialize.sh | 34 +--- .devcontainer/post-create.sh | 49 +---- .gitignore | 4 +- 9 files changed, 329 insertions(+), 278 deletions(-) create mode 100644 .claude/skills/devcontainer-setup/SKILL.md delete mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/devcontainer.json.example delete mode 100644 .devcontainer/docker-compose.devcontainer.yml delete mode 100644 .devcontainer/docker-compose.local.yml.example diff --git a/.claude/skills/devcontainer-setup/SKILL.md b/.claude/skills/devcontainer-setup/SKILL.md new file mode 100644 index 00000000000..34393f0731f --- /dev/null +++ b/.claude/skills/devcontainer-setup/SKILL.md @@ -0,0 +1,141 @@ +--- +name: devcontainer-setup +description: smalruby3-editor の devcontainer 設定 (.devcontainer/devcontainer.json) を対話的に作成する。テンプレート (.example) を基に、利用者の環境に合わせて mounts セクションを ON/OFF する。devpod / VS Code Dev Containers で開発したい人が初回に使う。 +--- + +# /devcontainer-setup - Interactive devcontainer.json builder + +`.devcontainer/devcontainer.json` は `.gitignore` 対象で、各自で `.example` から +コピーして自分の環境に合わせて mounts を編集する設計。本スキルは AskUserQuestion +を使ってこの作業を対話的に行う。 + +## 前提 + +- 本 repo の repo root から起動する +- `.devcontainer/devcontainer.json.example` が存在する +- `~/.gitconfig` `~/.config/gh` は普通にある前提 (devcontainer 起動の必須要件) + +## 実行フロー + +### Step 1: 既存 devcontainer.json の確認 + +```bash +if [[ -f .devcontainer/devcontainer.json ]]; then + 既存ファイルがある旨を伝えて、続行するか確認する + AskUserQuestion で「上書き」「中止」「diff を見る」を提示 +fi +``` + +中止が選ばれたら exit。 + +### Step 2: 利用シーンの確認 + +`AskUserQuestion` で: + +- 質問: 「どの IDE / ツールから devcontainer を起動しますか?」 +- header: "起動方法" +- options: + - "devpod (CLI)" — devpod up . で起動 + - "VS Code Dev Containers" — Reopen in Container で起動 + - "両方" — どちらでも動く設定にする + +この回答は後段で差を付ける必要は基本ないが、トラブルシュート時の案内に使う +(devpod の場合は GH_TOKEN export の警告を強めるなど)。 + +### Step 3: 個人マウントの選択 (multiSelect) + +`AskUserQuestion` で multiSelect: + +- 質問: 「以下の個人マウントのうち、有効にするものを選んでください」 +- header: "Mounts" +- multiSelect: true +- options: + - "ghq (~/ghq → /ghq)" — 関連 OSS の参照 + - "Claude Code 認証 (~/.claude.json)" — ログイン状態を引き継ぐ + - "Claude Code 設定・skills (~/.claude/settings.json, ~/.claude/skills, plugins, statusline-command.sh)" — グローバル設定を共有 + - "Claude Code memory (~/.claude/projects/-app)" — このプロジェクト固有 memory を共有 (他プロジェクト transcripts は隔離される) + +Claude 系は 3 つで 1 セットとして提示するのが分かりやすい (バラバラに選ぶと +認証だけあって memory が無い、といった中途半端な状態になる)。 + +### Step 4: ホスト側の状態確認 + +選ばれた mount のソースが host に存在するか確認: + +```bash +[[ -d "$HOME/ghq" ]] || 警告: ~/ghq が無いので作成するか確認 +[[ -f "$HOME/.claude.json" ]] || 警告: Claude Code を起動して認証してから再実行を推奨 +[[ -f "$HOME/.claude/settings.json" ]] || 警告: 同上 +``` + +存在しないソースが選ばれた場合は AskUserQuestion で: + +- "作成する (mkdir -p / touch)" — bind mount のソースを作る +- "選択を取り消す" — mount を有効化しない +- "気にせず進める" — devcontainer 起動時に bind 失敗の覚悟あり + +### Step 5: devcontainer.json の生成 + +`.devcontainer/devcontainer.json.example` を読み、選択された mount 行の +`// ` プレフィックスを除去する。生成方法: + +```bash +# 1. テンプレートをコピー +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json + +# 2. Edit ツールを使って、選択された各 mount 行のコメントを外す +# 例: ghq が選ばれた場合 +# `// ,"source=${localEnv:HOME}/ghq,target=/ghq,type=bind"` を +# `,"source=${localEnv:HOME}/ghq,target=/ghq,type=bind"` に置換 +``` + +実装上は AskUserQuestion で得た選択結果を順に Edit ツールで反映する。 + +### Step 6: GH_TOKEN 案内 + +macOS かどうかを確認し、macOS なら必ず案内: + +```text +devcontainer を起動する前に、以下をホスト側のシェルで実行してください: + + export GH_TOKEN=$(gh auth token) + +VS Code 利用の場合は、その export を実行した同じシェルから: + + code . + +devpod 利用の場合は: + + devpod up . --ide none + +(セッションを跨ぐと export が消えるので、毎日 1 回は必要) +``` + +### Step 7: 動作確認の促し + +最後に「テンプレートから生成した devcontainer.json はこちらです」と +パス (`.devcontainer/devcontainer.json`) を提示し、確認を促す。 + +`gh auth token` が取れているかと、選択した mount のソースが全部存在することを +最終チェックして報告する。 + +## エラーハンドリング + +- `.devcontainer/devcontainer.json.example` が無い → 「PR #687 がまだマージされていない可能性があります」と案内して exit +- repo root 以外で実行された → cd でない場合は exit +- 既存 `.devcontainer/devcontainer.json` がある → diff を見せて上書き確認 +- `gh` が host に無い → 「`brew install gh` してから再実行してください」 + +## 完了後の TODO 案内 + +- README に「次のステップ」として以下を提示: + - VS Code: `Cmd+Shift+P` → `Dev Containers: Reopen in Container` + - devpod: `devpod up . --ide none` + - 詳細は `.devcontainer/README.md` + +## 設計上の注意 + +- 本スキルは **生成だけ** を行う。devcontainer の起動・テストは行わない (副作用が大きいため) +- AskUserQuestion で multiSelect を使うことで、上述の Claude 3 マウントをまとめて選択させる +- 既存 devcontainer.json がある場合は安全側に倒す (上書きには明示的な確認) +- 編集には Edit ツールを使い、行ごとの正確な置換を行う (sed では空白や引用符でハマりやすい) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 371434fd0ab..d2df09b5095 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -5,84 +5,55 @@ NaCl Claude Code 利用ガイドライン **階層 B(OSS / 自社開発)** ## 設計方針 -- **既存の `docker-compose.yml` の `app` サービスを再利用** する。`docker-compose.devcontainer.yml` で mount のみ追加する override 構成 -- ホスト全体ではなく、**作業に必要なディレクトリのみ** をマウントする(物理アクセス範囲の制限) -- 既存の `bin/dx` / `bin/setup-worktree` / git worktree 運用と共存する -- VS Code Dev Containers と devpod の両方で動作する標準準拠 -- **devcontainer features は使わず**、`gh` / `claude` のインストールは `post-create.sh` で行う (devpod の features+compose 連携バグ回避のため。後述) +- **Dockerfile-only**: 既存 `Dockerfile` を直接ビルドする構成。`docker-compose` は使わない (devpod との相性問題を回避するため)。 +- **既存 `docker compose` ワークフローには影響なし**: `docker compose up app` / `docker compose run --rm app ...` / `bin/dx` は従来通り使える。devcontainer はそれと**別の経路**で動く。 +- **named volume を共有**: `docker compose run app npm install` で作った `node_modules` を devcontainer も使う。`bin/dx` の起動高速化メリットと同じ。git worktree 切替時も再インストール不要。 +- **個人別設定はテンプレート方式**: `.devcontainer/devcontainer.json` は `.gitignore` 対象。`devcontainer.json.example` をコピーして自分の mount を編集する。 -## マウント構成 +## 利用者の前提 -devcontainer のマウントは **共有用** と **個人用** を 2 つの compose ファイルに -分けている。 +このディレクトリの devcontainer は **devpod / VS Code Dev Containers で開発したい人** 向けです。`docker compose run --rm app ...` で十分な人は無視して構いません。 -### `docker-compose.devcontainer.yml` (commit、全員共通) +## 初回セットアップ -| ホスト側 | コンテナ内 | 用途 | -|---|---|---| -| `` | `/app` | 作業ディレクトリ(既存 compose と同じ) | -| `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー名・コミット署名 | -| `~/.config/gh` | `/root/.config/gh` | gh CLI 設定 | - -### `docker-compose.local.yml` (`.gitignore`、各自で `*.example` からコピー) - -ghq の有無、Claude Code の利用有無などはユーザーによって違うため、個人ごとに -override する。`.devcontainer/docker-compose.local.yml.example` をコピーして -自分の環境に合わせる: +### 1. テンプレートから自分の `devcontainer.json` を作る ```bash -cp .devcontainer/docker-compose.local.yml.example \ - .devcontainer/docker-compose.local.yml -# 不要な mount をコメントアウトして使う +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json ``` -template には以下がデフォルトで含まれる: +`.devcontainer/devcontainer.json` は `.gitignore` 対象なので、個人の編集は他開発者に共有されません。 -| ホスト側 | コンテナ内 | 用途 | -|---|---|---| -| `~/ghq` | `/ghq` | 関連 OSS の参照・push (使わない人はコメントアウト) | -| `~/.claude.json` | `/root/.claude.json` | Claude Code 認証 | -| `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 | -| `~/.claude/skills` | 同上 (ro) | 自作 skills | -| `~/.claude/plugins` | 同上 (ro) | プラグイン | -| `~/.claude/statusline-command.sh` | 同上 (ro) | カスタム statusline | -| `~/.claude/projects/-app` | `/root/.claude/projects/-app` | このプロジェクト固有 memory | - -`docker-compose.local.yml` が無い場合は `initialize.sh` が空の stub を自動生成 -するので、Claude Code を使わない・ghq を使わない人は何もせずそのまま動作する。 +### 2. 必要に応じて mounts を編集 -**マウントしないもの (全員)**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, -`~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, -`~/.claude/sessions/`, `~/.claude/history.jsonl`, `~/.claude/file-history/`, -`~/.claude/shell-snapshots/`** (他プロジェクトの転写・履歴の漏れを防ぐ)。 +`devcontainer.json` の `mounts` セクションの末尾にある **個人マウント** がコメントアウト状態で入っています: -### Claude Code の認証・設定・memory 共有について +- `~/ghq` (関連 OSS の参照) +- `~/.claude.json`, `~/.claude/skills`, `~/.claude/plugins`, etc. (Claude Code 設定) -`~/.claude` のうち、共有して安全なもの(グローバル設定・自作 skills・認証)だけを bind mount し、**他プロジェクトの transcripts や履歴は意図的に隔離** する設計。 +使うものだけアンコメントする。 -このプロジェクトのメモリ (`~/.claude/projects//memory/`) はホスト側スラッグがユーザーのパスに依存するため、`initialize.sh` がホスト側に **`~/.claude/projects/-app` → `~/.claude/projects/`** のシンボリックリンクを作成する。コンテナ内では workspace=`/app` のため slug=`-app` で固定され、リンク経由で同じ memory を読み書きできる。他のマシンや他の worktree に切り替えても、`initialize.sh` がそのときの host slug にリンクを張り直す。 +### 3. macOS: GH_TOKEN を export -### gh CLI の認証トークンについて (macOS 必須手順) - -macOS の `gh` は PAT を **Keychain** に保存するため、`~/.config/gh` を -bind mount しても `oauth_token` がコンテナに渡らない。devcontainer 起動前に -ホスト側のシェルで以下を実行して `GH_TOKEN` 環境変数として注入する: +macOS の `gh` は PAT を Keychain に保存するため、`~/.config/gh` を bind mount しても +`oauth_token` がコンテナに渡らない。devcontainer 起動前にホスト側のシェルで以下を実行: ```bash export GH_TOKEN=$(gh auth token) ``` -devcontainer.json の `remoteEnv.GH_TOKEN: "${localEnv:GH_TOKEN}"` 経由で -コンテナ内 `gh` が PAT を読み取れるようになる。未設定で devcontainer を -起動した場合、`initialize.sh` が警告を出す (devcontainer 自体は起動する)。 +`initialize.sh` が `GH_TOKEN` 未設定なら警告を出します。 ## 起動方法 ### VS Code Dev Containers -1. VS Code に「Dev Containers」拡張をインストール -2. リポジトリを VS Code で開く -3. コマンドパレットから **Dev Containers: Reopen in Container** +```bash +export GH_TOKEN=$(gh auth token) # 必須 (上述) +code /path/to/smalruby3-editor +``` + +VS Code 側で `Cmd+Shift+P` → **Dev Containers: Reopen in Container**。 ### devpod (CLI) @@ -92,64 +63,78 @@ brew install devpod devpod provider add docker # 起動 -cd -devpod up . +export GH_TOKEN=$(gh auth token) # 必須 +cd /path/to/smalruby3-editor +devpod up . --ide none # or --ide vscode -# Claude Code をコンテナ内で起動 -devpod ssh smalruby3-editor -- claude +# シェルに入る +devpod ssh smalruby3-editor # 停止 devpod stop smalruby3-editor + +# 完全削除 +devpod delete smalruby3-editor ``` -### docker compose 直接 (CLI only) +## マウント解説 -```bash -# devcontainer のオーバーレイを適用して起動 -docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontainer.yml up -d app +### 全員共通 (テンプレートに有効状態で含まれる) -# コンテナに入る -docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontainer.yml exec app bash -``` +| ホスト側 | コンテナ内 | 用途 | +|---|---|---| +| repo root | `/app` | 作業ディレクトリ | +| `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー情報 | +| `~/.config/gh` | `/root/.config/gh` | gh CLI 設定 | +| named volume `smalruby3-editor_smalruby3-editor_node_modules` | `/app/node_modules` | compose と共有 | +| named volume `smalruby3-editor_smalruby3-editor_root_npm` | `/root/.npm` | npm cache 共有 | +| named volume `smalruby3-editor_smalruby3-editor_root_cache` | `/root/.cache` | 各種 cache 共有 | + +### 個人 (テンプレートにコメントアウトで含まれる) -## 既存ワークフローへの影響 +| ホスト側 | コンテナ内 | 用途 | +|---|---|---| +| `~/ghq` | `/ghq` | 関連 OSS の参照・push | +| `~/.claude.json` | `/root/.claude.json` | Claude Code 認証 | +| `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 | +| `~/.claude/skills` | 同上 (ro) | 自作 skills | +| `~/.claude/plugins` | 同上 (ro) | プラグイン | +| `~/.claude/statusline-command.sh` | 同上 (ro) | カスタム statusline | +| `~/.claude/projects/-app` | `/root/.claude/projects/-app` | このプロジェクト固有 memory | -| 既存仕組み | 影響 | -|---|---| -| `docker compose run --rm app ...` | 変更なし。devcontainer を立てなくても従来通り動く | -| `bin/dx` | 変更なし(devcontainer 内では二重 docker を避けるラッパーを後続フェーズで検討) | -| `bin/setup-worktree` | `.devcontainer/post-create.sh` 経由で **worktree のときだけ** 実行(main checkout では skip) | -| git worktree | compose の `name: smalruby3-editor` 固定で named volume を共有 | +**マウントしないもの (全員)**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, +`~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, +`~/.claude/sessions/`, `~/.claude/history.jsonl`, `~/.claude/file-history/`, +`~/.claude/shell-snapshots/`** (他プロジェクトの転写・履歴の漏れを防ぐ)。 -## AWS CDK deploy について +### Claude Code memory の共有メカニズム -`~/.aws` はマウントしません。`cdk deploy` は人間が diff を見て発動する操作なので、 -**ホスト側で実行** することを推奨します。コンテナ内では `cdk synth` / `cdk diff` まで(AWS 認証不要)。 +このプロジェクトの memory (`~/.claude/projects//memory/`) はホスト側スラッグがユーザーのパスに依存するため、`initialize.sh` がホスト側に +**`~/.claude/projects/-app` → `~/.claude/projects/`** のシンボリックリンクを作成する。コンテナ内では workspace=`/app` のため slug=`-app` で固定され、リンク経由で同じ memory を読み書きできる。他のマシンや他の worktree に切り替えても、`initialize.sh` がそのときの host slug にリンクを張り直す。 -必要時のみ短命 STS token を環境変数で注入する運用も可能(要検討)。 +## なぜ docker-compose を使わないか -## トラブルシューティング +過去のリビジョンでは `dockerComposeFile` 形式で `app` サービスを共有していたが、以下の問題があり Dockerfile-only に移行した: -- **gh auth が効かない**: macOS では PAT が keychain に格納されるため、`export GH_TOKEN=$(gh auth token)` を実行してから devcontainer を起動する (上記「gh CLI の認証トークンについて」参照) -- **`/ghq` が空**: `ghq root` の出力が `~/ghq` 以外を指していないか確認 -- **ポート 8601 が見えない**: devcontainer 側はホスト bind を `!reset` で無効化している (重要: 通常 compose との衝突防止)。IDE 側の `forwardPorts: [8601]` が自動転送するため、VS Code / devpod 経由なら `localhost:8601` で開く -- **devpod up が `Could not resolve host` で失敗する**: 通常 compose (`docker compose up app`) と devpod 用の compose プロジェクトが同時に host port を取り合うと、後発側がネットワーク attach に失敗し、DNS が壊れた孤立コンテナになる。`docker compose stop app` で通常側を止めてから `devpod up .` を再実行する。本リポジトリでは `ports: !reset []` で host bind を無効化しているため再現しないはずだが、過去状態の container が残っていると同じ症状が出る可能性がある +1. **devpod での features 失敗**: `dockerComposeFile` + `features` の組み合わせで devpod が Dockerfile-with-features の build context を壊し、`COPY entrypoint.sh` が "not found" で失敗 +2. **port 衝突**: 通常 `docker compose up app` と devcontainer 用 compose が同時に host port 8601 を取り合い、後発の network attach が失敗 → DNS 孤立 +3. **個人別の override 困難**: compose の override 構文 (`docker-compose.local.yml`) は便利だが、devcontainer.json 自体の個人別カスタマイズには使えない -## なぜ devcontainer features を使わないか +Dockerfile-only にすることで: +- devpod でも features (claude-code, github-cli) がそのまま動く +- 通常 compose とは独立した volume / network のため衝突が起きない (named volume だけは共有して deps を再利用) +- `.devcontainer/devcontainer.json` 自体を `.gitignore` 対象にして個人別に編集できる -devcontainer.json の `features` (例: `ghcr.io/anthropics/devcontainer-features/claude-code`, -`ghcr.io/devcontainers/features/github-cli`) は、内部的に既存 Dockerfile を -**`Dockerfile-with-features` というラッパー** に書き換えてビルドする。 +通常開発で `docker compose` を使い続けたい人は、本ディレクトリを無視して従来通り `docker compose run --rm app ...` 等を使えば良い。 -`dockerComposeFile` 形式の devcontainer の場合、devpod はこのラッパービルド時に -**build context を破壊する**ことが分かった (本プロジェクトでの事例: -`COPY entrypoint.sh /app/entrypoint.sh` が "/entrypoint.sh: not found" で失敗)。 -VS Code Dev Containers では同じ設定で動作するため、devpod 固有の挙動と思われる。 +## AWS CDK deploy について -回避策として、features は使わず、`post-create.sh` で同等のインストールを行う: +`~/.aws` は **マウントしません**。`cdk deploy` は人間が diff を見て発動する操作なので、 +**ホスト側で実行** することを推奨します。コンテナ内では `cdk synth` / `cdk diff` まで (AWS 認証不要)。 -- `gh` CLI: apt 経由 (公式リポジトリの GPG キー登録 → install) -- `claude` (Claude Code): `npm install -g @anthropic-ai/claude-code` +## トラブルシューティング -これにより VS Code でも devpod でも同じ手順で動作する。`postCreateCommand` は -コンテナ作成ごとに 1 回走るが、`gh` / `claude` が既にあれば再インストールしない。 +- **gh auth が効かない**: macOS では PAT が keychain に格納されるため、`export GH_TOKEN=$(gh auth token)` を実行してから devcontainer を起動する +- **`/ghq` が空 or マウントエラー**: `devcontainer.json` の `mounts` で ghq 行をアンコメントしているか確認。`ghq root` が `~/ghq` 以外を指している場合は source パスを書き換える +- **node_modules が共有されない**: `docker compose run --rm app npm install` を一度実行して named volume を作ってから devcontainer を起動する +- **`devcontainer.json` が無いと言われる**: `.example` からコピーする (上記「初回セットアップ」を参照) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index cdcaa9c5274..00000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "smalruby3-editor (Claude Code)", - "dockerComposeFile": [ - "../docker-compose.yml", - "docker-compose.devcontainer.yml", - "docker-compose.local.yml" - ], - "service": "app", - "workspaceFolder": "/app", - "overrideCommand": false, - "remoteEnv": { - "GHQ_ROOT": "/ghq", - "GH_TOKEN": "${localEnv:GH_TOKEN}" - }, - "forwardPorts": [8601], - "initializeCommand": ".devcontainer/initialize.sh", - "postCreateCommand": ".devcontainer/post-create.sh", - "customizations": { - "vscode": { - "extensions": [ - "anthropic.claude-code", - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" - ] - } - } -} diff --git a/.devcontainer/devcontainer.json.example b/.devcontainer/devcontainer.json.example new file mode 100644 index 00000000000..42b00c5c981 --- /dev/null +++ b/.devcontainer/devcontainer.json.example @@ -0,0 +1,94 @@ +// .devcontainer/devcontainer.json +// +// このファイルは TEMPLATE です。`.devcontainer/devcontainer.json` は +// `.gitignore` 対象。各自で以下のようにコピーして使う: +// +// cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json +// # 必要に応じて mounts セクションをコメントアウト/編集 +// +// 詳細は .devcontainer/README.md を参照。 +// +// ## 設計方針 +// - **Dockerfile-only** 構成 (docker-compose は使わない)。devpod でも features が +// そのまま動く。 +// - 既存 docker-compose の named volume (`smalruby3-editor_*_node_modules` 等) を +// そのまま mount することで、`docker compose run app npm install` と +// devcontainer 起動の **両方が同じ deps を共有** する (起動高速化、git +// worktree 切替時もインストール不要)。 +// - 個人マウント (~/.claude*, ~/ghq 等) はテンプレートではコメントアウト。 +// 使うものだけアンコメントする。 +{ + "name": "smalruby3-editor", + + // 既存の Dockerfile をそのまま使う (context = repo root, dockerfile も repo root) + "build": { + "context": "..", + "dockerfile": "../Dockerfile" + }, + + "workspaceFolder": "/app", + "workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached", + + // entrypoint.sh の "build if not built" 処理を抑止して container を長寿命化 + "overrideCommand": true, + + "features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + + "remoteEnv": { + // macOS の gh は PAT を keychain に保存するため、devcontainer 起動前に + // export GH_TOKEN=$(gh auth token) + // が必要。詳細は README.md。 + "GH_TOKEN": "${localEnv:GH_TOKEN}" + }, + + "forwardPorts": [8601], + + "initializeCommand": ".devcontainer/initialize.sh", + "postCreateCommand": ".devcontainer/post-create.sh", + + "mounts": [ + // ----- 全員共通 (必須) ----- + // git の identity (commit author 等) + "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,readonly", + // gh CLI 設定 (PAT 本体は GH_TOKEN env で渡す。これは hosts.yml のユーザ情報のみ) + "source=${localEnv:HOME}/.config/gh,target=/root/.config/gh,type=bind", + + // ----- 既存 docker-compose との volume 共有 ----- + // `docker compose run app npm install` で生成された node_modules を + // devcontainer でもそのまま使う。volume 名は _ + // 形式で、project_name は docker-compose.yml の `name:` 指定で固定済み。 + "source=smalruby3-editor_smalruby3-editor_node_modules,target=/app/node_modules,type=volume", + "source=smalruby3-editor_smalruby3-editor_root_npm,target=/root/.npm,type=volume", + "source=smalruby3-editor_smalruby3-editor_root_cache,target=/root/.cache,type=volume" + + // ----- 個人マウント (必要なものだけアンコメント) ----- + + // 関連 OSS の参照 (smalruby/ruby-sdl2, scratchfoundation/scratch-editor 等) + // ,"source=${localEnv:HOME}/ghq,target=/ghq,type=bind" + + // Claude Code の認証・設定・skills を引き継ぐ + // 他プロジェクトの transcripts (~/.claude/projects/*, ~/.claude/sessions/, + // ~/.claude/history.jsonl, ~/.claude/file-history/) はマウントしないことで隔離する。 + // ,"source=${localEnv:HOME}/.claude.json,target=/root/.claude.json,type=bind" + // ,"source=${localEnv:HOME}/.claude/settings.json,target=/root/.claude/settings.json,type=bind,readonly" + // ,"source=${localEnv:HOME}/.claude/skills,target=/root/.claude/skills,type=bind,readonly" + // ,"source=${localEnv:HOME}/.claude/plugins,target=/root/.claude/plugins,type=bind,readonly" + // ,"source=${localEnv:HOME}/.claude/statusline-command.sh,target=/root/.claude/statusline-command.sh,type=bind,readonly" + + // このプロジェクト固有の Claude memory (initialize.sh が `-app` シンボリックリンクを作る) + // ,"source=${localEnv:HOME}/.claude/projects/-app,target=/root/.claude/projects/-app,type=bind" + ], + + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] + } + } +} diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml deleted file mode 100644 index 1a22711dad6..00000000000 --- a/.devcontainer/docker-compose.devcontainer.yml +++ /dev/null @@ -1,30 +0,0 @@ -services: - app: - # build context を明示的に repo root に固定。 - # 基底の docker-compose.yml は `build: .` だが、merge 後の context 解決を - # devpod / VS Code Dev Containers の両方で安定させるため、この overlay の - # 場所からの相対パスとして `..` (= repo root) を明示する。 - # これがないと devpod が build context を `.devcontainer/` 配下と - # 誤認し、`COPY entrypoint.sh /app/entrypoint.sh` が "not found" で失敗する。 - build: - context: . - dockerfile: Dockerfile - # devcontainer 用に長寿命化(既存 compose は `npm start` で短命) - command: sleep infinity - # ホスト側ポート公開を無効化する。 - # 通常 compose と devcontainer (VS Code / devpod) が同時に立ち上がると、 - # どちらも :8601 を host bind しようとして衝突し、結果として devcontainer 側が - # ネットワーク attach に失敗して container 内 DNS が壊れる事象があった。 - # devcontainer 側は forwardPorts (devcontainer.json) 経由で IDE がポート転送する - # ため、compose レベルで host bind する必要はない。 - ports: !reset [] - volumes: - # 全開発者で共通のマウント。個人依存のもの (~/ghq, ~/.claude 等) は - # docker-compose.local.yml に置く (gitignore、各自で .example からコピー)。 - - type: bind - source: ${HOME}/.gitconfig - target: /root/.gitconfig - read_only: true - - type: bind - source: ${HOME}/.config/gh - target: /root/.config/gh diff --git a/.devcontainer/docker-compose.local.yml.example b/.devcontainer/docker-compose.local.yml.example deleted file mode 100644 index 242d8a8332d..00000000000 --- a/.devcontainer/docker-compose.local.yml.example +++ /dev/null @@ -1,55 +0,0 @@ -# .devcontainer/docker-compose.local.yml -# -# 個人ごとに devcontainer を拡張するための override。`.gitignore` 対象。 -# 使い方: このファイルを `docker-compose.local.yml` にコピーし、自分の環境に -# 合わせて編集する。 -# -# cp .devcontainer/docker-compose.local.yml.example \ -# .devcontainer/docker-compose.local.yml -# -# devcontainer.json はこのファイルを dockerComposeFile に含めるため、Reopen -# in Container すると自動で適用される。空のままでも害はない。 -# -# 注意: -# - bind mount のソースが存在しないと devcontainer の起動が失敗するため、 -# 不要なエントリはコメントアウトしておく。 -# - Claude Code を使わない人は claude セクションを丸ごと省略してよい。 -# - ghq を使わない人は ghq セクションを省略してよい。 - -services: - app: - volumes: - # --- ghq (関連 OSS の参照) --- - # 自分の ghq root が ~/ghq でない場合は source を書き換える。 - - type: bind - source: ${HOME}/ghq - target: /ghq - - # --- Claude Code: グローバル設定・認証のみ共有 --- - # 他プロジェクトの transcripts / sessions / history は意図的に - # マウントしないことで漏れを防ぐ。詳細は README.md 参照。 - - type: bind - source: ${HOME}/.claude.json - target: /root/.claude.json - - type: bind - source: ${HOME}/.claude/settings.json - target: /root/.claude/settings.json - read_only: true - - type: bind - source: ${HOME}/.claude/skills - target: /root/.claude/skills - read_only: true - - type: bind - source: ${HOME}/.claude/plugins - target: /root/.claude/plugins - read_only: true - - type: bind - source: ${HOME}/.claude/statusline-command.sh - target: /root/.claude/statusline-command.sh - read_only: true - # このプロジェクトの memory のみ共有。initialize.sh が - # ${HOME}/.claude/projects/-app を host slug へのシンボリックリンクと - # して作成する。 - - type: bind - source: ${HOME}/.claude/projects/-app - target: /root/.claude/projects/-app diff --git a/.devcontainer/initialize.sh b/.devcontainer/initialize.sh index cbad9e744e1..fc847c168fb 100755 --- a/.devcontainer/initialize.sh +++ b/.devcontainer/initialize.sh @@ -2,35 +2,19 @@ # .devcontainer/initialize.sh # # Runs on the host before the devcontainer starts (devcontainer.json の -# initializeCommand から呼ばれる)。以下を行う: +# initializeCommand から呼ばれる)。役割: # -# 1. docker-compose.local.yml が無ければ空の stub を作る -# (devcontainer.json は常にこのファイルを参照するため) -# 2. GH_TOKEN が export されていなければ案内を出す -# (macOS の gh は PAT を keychain に保存するため、~/.config/gh を -# bind mount しただけではコンテナ内 gh から認証できない) -# 3. ホスト側に ~/.claude/projects/-app シンボリックリンクを作成 -# (Claude Code を使う人向けの per-project memory bridge) +# 1. GH_TOKEN が export されていなければ案内を出す +# (macOS の gh は PAT を keychain に保存するため、~/.config/gh の bind +# mount だけではコンテナ内 gh から認証できない) +# 2. Claude Code を使う人向けに ~/.claude/projects/-app シンボリックリンクを +# 作成 (per-project memory bridge) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# --- 1) docker-compose.local.yml の stub 作成 ----------------------------- -LOCAL_COMPOSE="${SCRIPT_DIR}/docker-compose.local.yml" -if [[ ! -f "${LOCAL_COMPOSE}" ]]; then - cat >"${LOCAL_COMPOSE}" <<'EOF' -# Auto-generated by .devcontainer/initialize.sh. -# 個人ごとの mount を追加したい場合は -# .devcontainer/docker-compose.local.yml.example を参考に書き換える。 -# このファイルは .gitignore 対象なので、個人の変更は他開発者に共有されない。 -services: - app: {} -EOF - echo "initialize: created empty ${LOCAL_COMPOSE} (edit to add personal mounts)" -fi - -# --- 2) GH_TOKEN チェック ------------------------------------------------- +# --- 1) GH_TOKEN チェック ------------------------------------------------- if [[ -z "${GH_TOKEN:-}" ]]; then if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then cat <<'MSG' >&2 @@ -49,9 +33,9 @@ MSG fi fi -# --- 3) Claude Code per-project memory bridge ---------------------------- +# --- 2) Claude Code per-project memory bridge ---------------------------- # Claude Code をホスト側にインストールしていないユーザは何もしない。 -# このセクションは本人が docker-compose.local.yml で +# このセクションは本人が devcontainer.json で # ~/.claude/projects/-app マウントを有効にしている場合に意味を持つ。 if [[ -d "${HOME}/.claude/projects" ]]; then WORKSPACE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index d25df0afe64..2bbdfb89e2a 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -4,60 +4,19 @@ # Runs inside the container after creation (devcontainer.json の # postCreateCommand から呼ばれる)。 # -# 役割: -# 1. gh CLI と Claude Code をインストール -# (devcontainer features は devpod + docker-compose で build context を -# 壊すバグがあるため、postCreate で代替する) -# 2. worktree であれば bin/setup-worktree を実行 (env コピー + npm install + build:dev) -# 3. main checkout であれば追加 setup なし -# -# tools のインストールが含まれるため、main checkout でも post-create.sh は -# 早期 exit せず常に実行する。 +# - worktree であれば bin/setup-worktree を実行 (env コピー + npm install + build:dev) +# - main checkout であれば既存の node_modules / dist / .env がそのままあるので +# 何もしない (gh / claude のインストールは devcontainer features が build 時に行う) set -euo pipefail cd /app -# --- 1) gh CLI のインストール (Debian bookworm) --- -if ! command -v gh >/dev/null 2>&1; then - echo "post-create: installing gh CLI..." - type -p curl >/dev/null || apt-get update -qq && apt-get install -y --no-install-recommends -qq curl ca-certificates - install -dm 0755 /etc/apt/keyrings - curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null - chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ - | tee /etc/apt/sources.list.d/github-cli.list >/dev/null - apt-get update -qq - apt-get install -y --no-install-recommends -qq gh - rm -rf /var/lib/apt/lists/* - echo "post-create: gh CLI installed: $(gh --version | head -1)" -else - echo "post-create: gh CLI already installed: $(gh --version | head -1)" -fi - -# --- 2) Claude Code のインストール (npm 経由) --- -# devpod 環境ではここでまれに "node: bad option: ..." を出して exit 1 する -# ことがあるが、container 自体は正常で、手動で再実行すれば成功する。 -# devcontainer up が失敗扱いにならないよう non-fatal にする。 -if ! command -v claude >/dev/null 2>&1; then - echo "post-create: installing Claude Code..." - if npm install -g @anthropic-ai/claude-code 2>&1 | tail -3 && command -v claude >/dev/null 2>&1; then - echo "post-create: Claude Code installed: $(claude --version 2>&1 | head -1)" - else - echo "post-create: WARNING: Claude Code install failed (devpod-related)." >&2 - echo "post-create: Install manually inside the container:" >&2 - echo "post-create: npm install -g @anthropic-ai/claude-code" >&2 - fi -else - echo "post-create: Claude Code already installed: $(claude --version 2>&1 | head -1)" -fi - -# --- 3) worktree のときだけ bin/setup-worktree --- GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null || echo "") GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || echo "") if [[ -z "${GIT_COMMON_DIR}" ]]; then - echo "post-create: not a git repository, skipping worktree setup" + echo "post-create: not a git repository, skipping setup" exit 0 fi diff --git a/.gitignore b/.gitignore index 26c1bdffe48..5ef55f91a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -160,5 +160,5 @@ infra/**/tmp/ infra/**/.cdk.staging/ infra/**/cdk-out-*/ -# devcontainer の個人用 mount override (initialize.sh が空 stub を生成する) -.devcontainer/docker-compose.local.yml +# devcontainer の個人別設定 (各自で .example からコピーして mounts を編集する) +.devcontainer/devcontainer.json From 7340bb3959242359b6dde7fb9a6a30fc5f731459 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Tue, 12 May 2026 01:43:23 +0900 Subject: [PATCH 6/7] fix(devcontainer): isolate ~/.claude.json from host (drop bind), add DISABLE_AUTOUPDATER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit container 内 Claude が auto-update すると host の ~/.claude.json まで version 表記が書き換わる問題を回避する。Anthropic 公式 (code.claude.com/docs/en/devcontainer) の "Avoid mounting host secrets" 原則とも合わせる。 ## 変更内容 - containerEnv.DISABLE_AUTOUPDATER=1 を追加 (auto-update 抑止) - 個人マウントテンプレートから ~/.claude.json bind を削除 (container 内では別途 `claude` でログイン。container fs で永続、 devpod stop/up は跨ぐが rebuild で消える) - skills/plugins/settings/statusline は ro bind のまま (host で編集、 container は読むだけなので汚染しない) - memory (~/.claude/projects/-app) は bind rw のまま (host との共有を維持) - README に新ポリシーを記載 - devcontainer-setup skill の選択肢から auth-only オプションを削除し、 Claude 系を 1 つのまとまりで提示する形に簡略化 ## 利用者が初回にやること container 起動後、初回のみ: claude で認証フロー (~/.claude.json は container fs 内に作成、host を汚さない)。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/devcontainer-setup/SKILL.md | 12 +++++----- .devcontainer/README.md | 22 +++++++++++++++--- .devcontainer/devcontainer.json.example | 26 ++++++++++++++++++---- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/.claude/skills/devcontainer-setup/SKILL.md b/.claude/skills/devcontainer-setup/SKILL.md index 34393f0731f..9b06cd3c94d 100644 --- a/.claude/skills/devcontainer-setup/SKILL.md +++ b/.claude/skills/devcontainer-setup/SKILL.md @@ -51,12 +51,12 @@ fi - multiSelect: true - options: - "ghq (~/ghq → /ghq)" — 関連 OSS の参照 - - "Claude Code 認証 (~/.claude.json)" — ログイン状態を引き継ぐ - - "Claude Code 設定・skills (~/.claude/settings.json, ~/.claude/skills, plugins, statusline-command.sh)" — グローバル設定を共有 - - "Claude Code memory (~/.claude/projects/-app)" — このプロジェクト固有 memory を共有 (他プロジェクト transcripts は隔離される) + - "Claude Code 設定・skills (~/.claude/settings.json, skills, plugins, statusline-command.sh, projects/-app)" — host で開発した skills + このプロジェクトの memory を container に持ち込む。container 内 Claude の初回起動時にログインが必要 (auth は意図的に共有しない) -Claude 系は 3 つで 1 セットとして提示するのが分かりやすい (バラバラに選ぶと -認証だけあって memory が無い、といった中途半端な状態になる)。 +**重要**: `~/.claude.json` の bind は **オプションから外している**。container と +host の auth/version 分離のため、container は別途ログインする設計。auto-update +抑止のため containerEnv.DISABLE_AUTOUPDATER=1 は常に有効。詳細は +.devcontainer/README.md の「Claude Code 認証は マウントしない」セクション参照。 ### Step 4: ホスト側の状態確認 @@ -64,7 +64,7 @@ Claude 系は 3 つで 1 セットとして提示するのが分かりやすい ```bash [[ -d "$HOME/ghq" ]] || 警告: ~/ghq が無いので作成するか確認 -[[ -f "$HOME/.claude.json" ]] || 警告: Claude Code を起動して認証してから再実行を推奨 +[[ -d "$HOME/.claude/skills" ]] || 警告: Claude Code を起動したことが無い可能性 [[ -f "$HOME/.claude/settings.json" ]] || 警告: 同上 ``` diff --git a/.devcontainer/README.md b/.devcontainer/README.md index d2df09b5095..302caed9213 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -95,12 +95,28 @@ devpod delete smalruby3-editor | ホスト側 | コンテナ内 | 用途 | |---|---|---| | `~/ghq` | `/ghq` | 関連 OSS の参照・push | -| `~/.claude.json` | `/root/.claude.json` | Claude Code 認証 | -| `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 | +| `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 (host で編集、container は読むだけ) | | `~/.claude/skills` | 同上 (ro) | 自作 skills | | `~/.claude/plugins` | 同上 (ro) | プラグイン | | `~/.claude/statusline-command.sh` | 同上 (ro) | カスタム statusline | -| `~/.claude/projects/-app` | `/root/.claude/projects/-app` | このプロジェクト固有 memory | +| `~/.claude/projects/-app` | `/root/.claude/projects/-app` (rw) | このプロジェクト固有 memory (host と共有) | + +### Claude Code 認証は **マウントしない** (重要) + +`~/.claude.json` は host のものを意図的に共有しない。理由: + +- container 内 Claude が auto-update すると host の version 表記を書き換える (host + 側 Claude との不整合発生源になる) +- host secrets / API tokens を container に持ち込まないという Anthropic 公式の + 原則とも整合する + +代わりに container 内では **初回 `claude` 起動時にログインを行う**。container fs に +保存された auth は `devpod stop` / `devpod up` を跨いで永続。`devpod delete` または +devcontainer rebuild で消えるが、再ログインは数十秒で済む。 + +加えて **`DISABLE_AUTOUPDATER=1`** を `containerEnv` に設定済み。container 内 +Claude は自動アップデートしない。version 固定は IDE 側で features が install する +最新版を build 時に決め打ちする扱い。 **マウントしないもの (全員)**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, `~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, diff --git a/.devcontainer/devcontainer.json.example b/.devcontainer/devcontainer.json.example index 42b00c5c981..dac8b9c02ea 100644 --- a/.devcontainer/devcontainer.json.example +++ b/.devcontainer/devcontainer.json.example @@ -44,6 +44,12 @@ "GH_TOKEN": "${localEnv:GH_TOKEN}" }, + "containerEnv": { + // container 内 Claude が host の ~/.claude.json のバージョンを + // 勝手に書き換えるのを防ぐ。host との version drift 防止。 + "DISABLE_AUTOUPDATER": "1" + }, + "forwardPorts": [8601], "initializeCommand": ".devcontainer/initialize.sh", @@ -69,10 +75,22 @@ // 関連 OSS の参照 (smalruby/ruby-sdl2, scratchfoundation/scratch-editor 等) // ,"source=${localEnv:HOME}/ghq,target=/ghq,type=bind" - // Claude Code の認証・設定・skills を引き継ぐ - // 他プロジェクトの transcripts (~/.claude/projects/*, ~/.claude/sessions/, - // ~/.claude/history.jsonl, ~/.claude/file-history/) はマウントしないことで隔離する。 - // ,"source=${localEnv:HOME}/.claude.json,target=/root/.claude.json,type=bind" + // ---- Claude Code 関連 (Anthropic 公式 + 安全側を考慮した設計) ---- + // + // - **認証 (~/.claude.json) は mount しない**。container 内で初回 `claude` 起動時に + // 別途ログインする。container の auth state は container 内 fs に残り、 + // `devpod delete` または devcontainer rebuild まで永続。host の ~/.claude.json + // は触らない (auto-update でも host が汚れない)。 + // - **DISABLE_AUTOUPDATER=1** (上記 containerEnv) で auto-update も止める。 + // host との version drift を防ぐ。 + // - skills / plugins / settings / statusline は host から **read-only** + // bind で持ち込む。container 内では書き換えできないので汚染しない。 + // - **memory のみ bind rw** で host と共有 (このプロジェクト固有の memory + // なので host 側 Claude と相互に読み書きする想定)。 + // - その他 (~/.claude/projects/*, sessions, history, file-history) は + // マウントしないことで他プロジェクトの transcripts を隔離。 + + // host の skills / plugins / 設定を読むだけ (container は書き換え不可) // ,"source=${localEnv:HOME}/.claude/settings.json,target=/root/.claude/settings.json,type=bind,readonly" // ,"source=${localEnv:HOME}/.claude/skills,target=/root/.claude/skills,type=bind,readonly" // ,"source=${localEnv:HOME}/.claude/plugins,target=/root/.claude/plugins,type=bind,readonly" From ee62654d6f4585ab0b09d0d23e45fd905f19df38 Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Tue, 12 May 2026 11:26:27 +0900 Subject: [PATCH 7/7] docs(devpod): add development workflow guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devpod を主体とする日常開発ワークフローのガイドを追加。compose ベースの 旧ワークフローと何が違うか、特に git worktree との組み合わせで何が 変わるかを明示する。 主な内容: - 起動からの 1 日のルーチン (export GH_TOKEN → devpod up → tmux) - compose との挙動差分一覧 - named volume 共有による npm install 二重実行の回避 - worktree ごとに別の devpod workspace (= 別 container) になる点 - 複数 workspace 同時起動の port 衝突 (devpod が auto-assign) - できなくなること (bin/dx の手軽さ、host CDK 認証の container 持ち込み等) - トラブルシューティング (gh / claude / worktree / tmux) Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/rules/devpod-workflow.md | 276 +++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 .claude/rules/devpod-workflow.md diff --git a/.claude/rules/devpod-workflow.md b/.claude/rules/devpod-workflow.md new file mode 100644 index 00000000000..9fd8449ad8d --- /dev/null +++ b/.claude/rules/devpod-workflow.md @@ -0,0 +1,276 @@ +# devpod-based Development Workflow + +`/Users/kouji/work/smalruby/smalruby3-editor` の開発は **devpod 経由で devcontainer 内で行う** ことを基本とする。社内ガイドライン (NaCl Claude Code 利用ガイドライン v0.2) の階層 B 要件 (隔離環境推奨) に準拠するためで、Claude Code をホスト直接実行しない。 + +## 前提と全体像 + +| 観点 | 値 | +|---|---| +| 主用途 | devcontainer の中で開発 (Claude Code, npm, git すべて中で完結) | +| エディタ | devpod ssh + tmux (VS Code Dev Containers でも可) | +| host から見える環境 | ブラウザ (port forwarding 経由)、git の push/PR (gh CLI 経由)、CDK deploy | +| 採用していないもの | `docker compose run --rm app ...`, `bin/dx`, `bin/setup-worktree` (compose 前提なので不要) | +| 例外的に host で必要 | `docker compose` 自体は dev server を host から開きたい人のために残してあるが、本人は使わない | + +## 起動からの流れ (毎日のルーチン) + +### 1. 初回 / `devpod delete` 後のセットアップ + +```bash +# 一度だけ: provider 登録 +brew install devpod +devpod provider add docker + +# 各 worktree で必要: devcontainer.json を個人用にカスタマイズ +# 詳細は .devcontainer/README.md +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json +# 必要なら mounts を編集 (Claude マウントの有効化など) + +# または対話的に: /devcontainer-setup スキル +``` + +### 2. ホストシェルでの 1 行ルーチン + +```bash +export GH_TOKEN=$(gh auth token) # macOS keychain 由来。毎セッション必要 +cd /Users/kouji/work/smalruby/smalruby3-editor +devpod up . --ide none # 初回 build はかかる、以降は数秒 +``` + +### 3. tmux で入って作業 + +```bash +devpod ssh smalruby3-editor -- bash -lc 'tmux new -A -s work' +``` + +container 内で: +```bash +cd /app +claude # 初回ログインが必要 (host とは分離) +npm run lint # そのまま動く +npm start # dev server をここで起動 → forwardPorts でホストに公開 +``` + +### 4. 終了 / 一時停止 + +```bash +# 一時停止 (container 残す、状態保持) +devpod stop smalruby3-editor + +# 完全削除 (再ログイン必要、image は残る) +devpod delete smalruby3-editor +``` + +## docker compose ベースの旧ワークフローとの差分 + +以下は **本ガイドの利用者 (devpod 主体) は基本やらない** が、他のメンバーが +compose で動かしている場合に違いを理解しておくため。 + +| やりたいこと | 旧 (compose) | 新 (devpod) | +|---|---|---| +| dev server 起動 | `docker compose up app` | container 内で `npm start`, host の `localhost:8601` (forwardPorts で自動転送) | +| lint 1 回 | `bin/dx bash -c "npm run lint"` | container の tmux で `npm run lint` | +| 個別 jest | `bin/dx bash -c "cd packages/scratch-gui && npm exec jest test/xxx"` | 同上、container 内で実行 | +| npm install | `docker compose run --rm app npm install` | container 内で `npm install` (named volume で host 側 compose とも共有される) | +| `docker compose run` でテスト | `docker compose run --rm app npm test` | container 内で `npm test` | +| 単独のフリー仕事 (curl 確認等) | host 上で実行 | 任意。container 内でも OK | +| Playwright MCP | host の Playwright が動く想定 | host の Playwright が container の `localhost:8601` (forwardPorts) を見る | +| CDK deploy | host の `aws-vault` 等で | **同じく host で実行** (container には `~/.aws` を mount していないため) | + +### named volume の共有挙動 (重要) + +`.devcontainer/devcontainer.json` は **既存 docker-compose と同じ named volume** を +マウントするため、以下が同期される: + +- `/app/node_modules` ↔ compose の `smalruby3-editor_smalruby3-editor_node_modules` +- `/root/.npm` ↔ `smalruby3-editor_smalruby3-editor_root_npm` +- `/root/.cache` ↔ `smalruby3-editor_smalruby3-editor_root_cache` + +これにより、devpod の container で `npm install` した結果を `docker compose run app` +でも (またはその逆でも) そのまま使える。**両方の経路を混在させても OK**。 + +## git worktree との組み合わせ + +git worktree を作るときの挙動が **compose 時代と大きく違う** ので注意。 + +### compose 時代の挙動 (参考) + +- `docker-compose.yml` の `name: smalruby3-editor` で project 名固定 +- `docker compose run app` をどの worktree から実行しても、**同じ container を共有** (一個だけ生きている) +- container の `/app` は、container 起動時にバインドされた特定 worktree のパス +- 別 worktree の作業をするには `docker compose down && cd <他 worktree> && docker compose up` +- 結果: **同時に動かせる worktree は実質 1 つ** + +### devpod 時代の挙動 + +- devpod の workspace ID は **host の絶対パス** から生成される (`smalruby3-editor` と `smalruby3-editor-tutorial` は別 workspace) +- worktree ごとに **別の workspace = 別の container** が立ち上がる +- 別 container でも mount 経由で **以下は共有**: + - named volume の `node_modules` / `.npm` / `.cache` (再 install 不要) + - bind の `~/.claude/skills` / `~/.claude/projects/-app` (skills と memory) + - bind の `~/.gitconfig` / `~/.config/gh` / `~/ghq` +- 別 container なので **以下は per-worktree**: + - container 内 `~/.claude.json` (Claude 認証) → **各 worktree で初回再ログインが必要** + - container 内のシェル履歴 / tmux セッション / インストールしたアドホックなツール + - port forwarding (後述) + +### worktree を作るときの手順 + +```bash +# 1. host で worktree を作る (CLAUDE.md / git-workflow.md の従来手順) +git worktree add ../smalruby3-editor- -b / develop +cd ../smalruby3-editor- + +# 2. env ファイルを copy する (compose も devpod も両方使えるよう) +bin/sync-worktree-env + +# 3. devcontainer.json を個人用にコピー (worktree 内は .gitignore 対象なので別途必要) +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json +# 必要なら mounts を編集 + +# 4. devpod 起動 +export GH_TOKEN=$(gh auth token) +devpod up . --ide none + +# 5. tmux で入る +devpod ssh smalruby3-editor- -- bash -lc 'tmux new -A -s work' + +# 6. 初回 Claude ログイン (worktree ごとに必要) +claude +``` + +### `bin/setup-worktree` の扱い + +`bin/setup-worktree` は **docker compose 経由で npm install + build:dev を行う** +スクリプトで、compose 利用者向けに作られている。devpod 主体ならこれは不要。 + +ただし named volume が空 (一度も install していない) の状態で初めて worktree を +立てるときは、最低 1 度は何らかの形で `npm install` を走らせる必要がある。 +container 内で: + +```bash +docker compose run app npm install # host 上で +# or +# devpod 内で: +cd /app && npm install +``` + +どちらでも結果は named volume に入り、以降の worktree で再利用される。 + +### worktree を捨てるとき + +```bash +# host で +cd /Users/kouji/work/smalruby/smalruby3-editor # main checkout に戻る +devpod delete smalruby3-editor- # devpod workspace を削除 +git worktree remove ../smalruby3-editor- # worktree を削除 +git branch -d / # ブランチ削除 (merge 済みなら) +``` + +`devpod delete` を忘れると container が残りリソースを食う。 + +### worktree を複数同時に走らせるときの注意 + +devpod では **複数 workspace が同時に container 起動可能**。これは compose と +違って **完全並列開発が可能** だが、以下の制約がある: + +- **port 衝突**: 各 workspace の `forwardPorts: [8601]` が同時に host port 8601 を + 欲しがる。最初の workspace は 8601、次は 8602 など devpod が自動で別 port に + 振り分ける (`devpod list` で確認できる) +- **メモリ消費**: container 1 つあたり数百 MB〜数 GB。worktree が 5 つあれば + 全部 up しているとそれなりに重い +- **アクティブでない workspace は `devpod stop`**: 停止しても state は残るので + すぐ再開できる +- **named volume は全 workspace で共有**: `npm install` が 1 つの workspace で + 進行中に他 workspace で並列 install すると競合する。**install は同時に走らせない** + +## port forwarding と dev server + +| 何を見たいか | 操作 | +|---|---| +| dev server (smalruby editor) | container 内で `npm start` → host の `localhost:8601` (devpod が自動転送) で開く | +| smalruby3-gui (VNC) | これは旧 compose の `smalruby3-gui` サービス専用。devpod ではこのサービス分は立ち上げない。VNC 確認は host から `docker compose up smalruby3-gui` で起動 (devpod と独立) | +| 自分の Playwright MCP | host の Playwright が `localhost:8601` を見るだけ | + +`devpod list` で実際に forward されている port を確認できる。 + +## Claude Code を container 内で動かす + +### 認証 + +- 初回起動時 `claude` でログイン (host とは分離) +- `~/.claude.json` は **container 内 fs に保存**、`devpod stop/up` を跨ぐが + `devpod delete` で消える。3 ヶ月に 1 度くらい再ログイン覚悟 + +### 設定の上書き不可 + +- `~/.claude/settings.json` は read-only bind なので container 内では変更不能 +- 変更したいときは host 側 `~/.claude/settings.json` を編集 → devcontainer 再起動 + +### skills, plugins + +- host で開発した skills / plugins が read-only bind されて container でも使える +- container 内で skills を編集することはできない (ro) + +### memory (このプロジェクト固有) + +- `~/.claude/projects/-app` を bind rw で共有 +- container 内 Claude が memory を書くと host 側 Claude にも反映 (双方向) + +### auto-update + +- `containerEnv.DISABLE_AUTOUPDATER=1` で抑止済み +- container の Claude version は features が build 時に固定したものに留まる +- バージョン更新したい時は devcontainer rebuild (`devpod delete` → `devpod up`) + +## できなくなること / 制約 + +| 旧 (compose) | 新 (devpod 主体) | 理由 | +|---|---|---| +| `bin/dx bash -c "..."` の手軽さ | container 内で同じことを 1 コマンドで | host 上 docker run が不便なので tmux に入って実行 | +| 1 つの container で全 worktree を切替使用 | 各 worktree が独立 container | devpod の workspace 設計上 | +| host の `~/.aws` を使った CDK deploy を container 内から | **そのまま host で実行** | secret leak 防止のため container には mount しない | +| host の Claude Code 認証を共有 | container 内で別途ログイン | host secrets 持ち込み禁止原則 | +| smalruby3 (Ruby gem) の SDL2 GUI | host で動かす (X server / SDL2 が container 内に無い) | devcontainer は GUI 想定外 | +| docker compose の他サービス (`infra`, `smalruby3-gui`) との同時起動 | devpod は `app` 相当のみ。他サービスは host から `docker compose up ` | devcontainer は単一サービス | + +## トラブルシューティング + +### `gh auth status` が失敗 + +- ホスト側で `export GH_TOKEN=$(gh auth token)` を忘れている +- 古い shell で `devpod up` していて env が更新されていない +- 対処: 新しいシェルで export してから `devpod up . --ide none` + +### container 内の `npm install` が遅い / 競合 + +- 別 worktree の workspace で並行 install していないか確認 (`devpod list`) +- named volume が壊れていたら `docker volume rm smalruby3-editor_smalruby3-editor_node_modules` でリセット (要 npm install からやり直し) + +### `claude` がいない / バージョンが古い + +- features は最新を install する。固定したい時は Dockerfile で `npm install -g @anthropic-ai/claude-code@X.Y.Z` +- バージョン上げたい: `devpod delete` → `devpod up` で features の最新を取り直し + +### worktree の `docker compose run app ...` が devpod workspace を壊した + +`docker compose` は `name: smalruby3-editor` 固定で project を作る。devpod は +`default-XXX-` prefix で別 project を作る。両者は基本独立だが、**named volume と +port 8601 は共有リソース** なので衝突しうる: + +- compose 側で port 8601 を握っていると devpod 側が conflict (DNS 孤立) +- 対処: `docker compose stop app` で compose 側を止める、または devpod を先に + 落としてから compose 起動 + +### tmux session が消えた + +`devpod stop` は container を止めるので session も消える。**永続化させる必要が +ある作業 (長時間 build / 監視) は tmux + nohup or systemd など別途工夫が必要**。 + +## 参考 + +- 利用ポリシー: NaCl Claude Code 利用ガイドライン v0.2 §3-2 +- devcontainer 設計: `.devcontainer/README.md` +- 対話的セットアップ: `/devcontainer-setup` skill +- 旧 worktree 手順 (compose 利用者向け): `.claude/rules/git-workflow.md` の「Git Worktree Setup」 +- Anthropic 公式 devcontainer ドキュメント: https://code.claude.com/docs/en/devcontainer