Skip to content

feat(container): AI 設定を共通 / アカウントグループの 2 層に分ける (PLAN39 PR2) - #124

Merged
takemi-ohama merged 2 commits into
release/PLAN39from
feature/PLAN39-entrypoint
Aug 29, 2026
Merged

takemi-ohama merged 2 commits into
release/PLAN39from
feature/PLAN39-entrypoint

Conversation

@takemi-ohama

Copy link
Copy Markdown
Contributor

Pull Request

概要

PLAN39 の 2 本目。entrypoint の symlink 機構を 2 系統にし、認証情報と会話履歴(分類 B)を
PR1 で用意したグループボリューム /persistent/group へ、契約やテナントに紐づかない共通資産
(分類 A)を従来どおり /persistent/ai へ振り分けます。あわせて入れ子パスの不具合を直します。

設計判断は issues/PLAN39_account-group-volume-separation.md(モード: architecture)に
残しています。この PR でプランの不変条件を 1 つ反転させたので、その理由を下に書きます。

関連 Issue

プランからの設計変更(不変条件の反転)

プランは「~/.claude を実ディレクトリにし、その配下に A / B 双方の symlink を並べる」と
書いていましたが、実機を見て反転しました。

稼働中コンテナ (carmo-ai-dev-1) の /persistent/ai/.claude の子要素は 30 件あり、
プランの分類表が名指ししているのは 7 件だけです。

$ docker exec carmo-ai-dev-1 sh -c 'ls -d /persistent/ai/.claude/* /persistent/ai/.claude/.[!.]* | xargs -n1 basename'
.credentials.json .last-cleanup .last-update-result.json .ndf-retention-checked
.ndf-retention.lock .ndf-statusline-backup.json .ndf-statusline.lock CLAUDE.md
backups cache commands daemon daemon.log debug file-history history.jsonl ide jobs
logs mcp-needs-auth-cache.json ndf-statusline.sh paste-cache plugins projects
session-env sessions settings.json shell-snapshots skills tasks

$ docker exec carmo-ai-dev-1 sh -c 'du -sh /persistent/ai/.claude/projects /persistent/ai/.claude/plugins'
1.1G  /persistent/ai/.claude/projects
222M  /persistent/ai/.claude/plugins

実ディレクトリ + 列挙方式にすると、列挙外の子(projects = 1.1GB の会話ログ、sessions、
tasks 等)がコンテナ再作成のたびに揮発
します。プラン自身が「会話履歴には顧客情報が
入りうる」として history.jsonl を分類 B にしている以上、これは意図に反します。
Claude Code は版が上がるたびに新しい子ディレクトリを作るため、列挙漏れは今後も起きます。

そこで既定を反転しました。

プラン当初 本 PR
~/.claude 実ディレクトリ symlink(向き先 /persistent/group/.claude)
既定の分類 未列挙は揮発 未列挙は B(グループ側)
列挙するもの A と B の両方 A だけ(5 件)

~/.claude が symlink であること自体は現行 main と同じで、変わるのは向き先だけです。
AC4(readlink -f ~/.claude/plugins が共通側を指す)は 2 段の symlink を経由して満たします。
プラン文書の不変条件 / AC6 / 分類表 / Task 4 / 切り戻し手順を、この変更に合わせて更新しました
(前提 20 として実測を追記)。

変更点

containers/base/entrypoint.sh

symlink 処理を関数へ切り出し、DEVBASE_ENTRYPOINT_LIB_ONLY=1 でテストから直接呼べるように
しました。エントリの分類は 3 つの配列で表します。

配列 内容 張る先
DEVBASE_SHARED_SETTINGS .codex .serena .ssh .kiro share /persistent/ai/<entry>
DEVBASE_GROUP_SETTINGS .claude.json .claude .gemini /persistent/group/<entry>
DEVBASE_SHARED_CLAUDE_SETTINGS plugins skills commands CLAUDE.md settings.json /persistent/group/.claude/<x> → /persistent/ai/.claude/<x>

入れ子パスの不具合 2 件を修正(プラン 前提 5):

不具合 原因 修正
.credentials.json が壊れた symlink になる 実体側の親ディレクトリが無く touch が No such file or directory で落ちる link 側・実体側の双方で mkdir -p "$(dirname ...)"
history.jsonl がディレクトリとして作られ Claude Code が追記できない ファイル判定が *.json グロブで .jsonl にマッチしない 拡張子判定をやめ DEVBASE_FILE_ENTRIES の明示列挙にする

default グループの初回シード: グループ側に実体が無いエントリだけ、/persistent/ai から
コピーして初期化します(move ではないので切り戻し時に元が残る)。非 default では走らせません
(走らせると分離の意味が失われる)。.claude のシードでは分類 A の 5 件を除外します
(除外しないと直後の symlink 生成が消すだけの無駄なコピーになる)。

空の named volume は root 所有で作られ uid 1000 では書けないため、書けなければ chown します
(プラン 前提 18)。テストのように最初から書ける場所では sudo を呼びません。

tests/containers/test_entrypoint_ai_settings.py(新規・21 件)

Docker に依存せず、一時ディレクトリを /persistent/ai / /persistent/group / $HOME に
見立てて関数を直接呼びます。2 系統の振り分け・入れ子パス・既存状態からの張り替え・
初回シードの 4 グループ。

やらないこと(スコープ外)

  • CLOUDSDK_CONFIG / GOOGLE_WORKSPACE_CLI_CONFIG_DIR と GCP_AUTH_MODE — PR3
  • snapshot のグループ対応・devbase status 表示・ボリューム構造のドキュメント — PR4
  • Google 認証の手順書 — PR5

満たす受け入れ条件

  • AC3: 異なるグループのコンテナが互いの認証を参照しない
  • AC4: 共通資産が重複しない(readlink -f ~/.claude/plugins が同一実体)
  • AC6: 入れ子パスの symlink が壊れない / history.jsonl がディレクトリにならない
  • AC8: default は初回シードで再ログインが発生しない
  • AC11: 鍵モードの経路(symlink ブロックを動かしていないため退行しない)

影響と互換性

  • base イメージの再ビルドが必要です(devbase build --no-cache)。devbase up 単体では
    entrypoint の変更が反映されません
  • 初回起動時、default グループでは /persistent/ai から /persistent/group への
    コピーが 1 回だけ走ります。実機の実測で 1.3GB 程度あるため初回だけ起動が伸びます
    (2 回目以降は何もしません)
  • 非 default グループは gcloud / gws を含め初回 1 回の認証が必要です(AC8 の但し書き)

動作確認

  • uv run pytest が green
  • bash -n containers/base/entrypoint.sh が pass
  • 実機での devbase build --no-cache 後の確認は PR3 とまとめて release PR で行う

自動テスト

$ uv run pytest tests/ -q
1545 passed in 60.78s                        # exit=0

$ uv run pytest tests/containers/ -q
53 passed in 21.41s                          # exit=0

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5oA2PqY6UX2Ca3t78886t

entrypoint の symlink 機構を 2 系統にし、認証と会話履歴 (分類 B) を
/persistent/group へ、契約に紐づかない共通資産 (分類 A) を /persistent/ai へ
振り分ける。あわせて入れ子パスの不具合を直す。

- `~/.claude` の既定をグループ側へ倒す。symlink である点は現行と同じで、
  向き先だけを /persistent/group/.claude に変え、その配下へ共通資産 5 件
  (plugins / skills / commands / CLAUDE.md / settings.json) の symlink を張る。
  実機の `~/.claude` には子要素が 30 件あり、列挙方式では projects (1.1GB の
  会話ログ) のような未列挙の子が黙って揮発するため (プラン 前提 20)
- 入れ子パス対応 — link 側と実体側の**双方**で親ディレクトリを作る。以前は
  実体側の作成が No such file or directory で落ち、壊れた symlink が残っていた
- ファイル / ディレクトリの判定を拡張子 (`*.json`) から明示の一覧へ改める。
  `.jsonl` がマッチせず history.jsonl がディレクトリとして作られていた
- default グループの初回シード — 共通側の分類 B を**コピー**して初期化する
  (move ではないので切り戻し時に元が残る)。非 default では走らせない。
  `.claude` のシードは分類 A の 5 件を除外する
- 空の named volume は root 所有で作られるため、書けなければ chown する

プラン文書の不変条件・AC6・分類表・Task 4・切り戻し手順を、この設計変更に
合わせて更新した。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5oA2PqY6UX2Ca3t78886t

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | codex | APPROVE

変更内容に対する修正提案はありません。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | gemini | APPROVE

設計・後方互換性・テストともに堅牢です。1点だけ、既存実装から引き継がれている初期設定ファイル(settings.json)の揮発についてインラインで指摘しました。

Comment thread containers/base/entrypoint.sh
Dockerfile が書き込む hooks 設定 (~/.claude/settings.json) は、symlink 張り替えの
rm -rf で消えていた。settings.json を共通側の永続化対象に加えたことで、代わりに
空のプレースホルダが /persistent/ai へ残る形になる。張り替えの前に共通側へ
コピーして、初回起動で hooks が失われないようにする。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5oA2PqY6UX2Ca3t78886t
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

🔧 /ndf:fix 最終スイープ サマリ

最終 APPROVE ラウンド(gemini)に残っていたインライン指摘 1 件を処理し、open review thread を 0 件にしました。

対応件数: critical=0 / major=1 / minor=0 (合計 1 件)
deferred: 0 件 / rejected: 0 件
commit: 29b38e7
CI: NONE(このリポジトリはブランチに対する GitHub Actions チェックなし。ローカルで uv run pytest -q → 1548 passed)

詳細

# ファイル 指摘 対応
1 containers/base/entrypoint.sh:293 Dockerfile が初期生成する ~/.claude/settings.json(hooks 設定)が symlink 張り替えの rm -rf で消え、永続側には空ファイルが残る 修正 — devbase_seed_image_claude_settings() を追加し、symlink を張る前に共通側へ退避

修正の要点

  • ~/.claude が symlink でない(=初回起動)ときだけ、DEVBASE_SHARED_CLAUDE_SETTINGS の 5 件を $home_root/.claude/<entry> から /persistent/ai/.claude/<entry> へコピーする。
  • コピーは既存の devbase_seed_entry() を再利用。dest が既にあれば何もしないので、利用者が編集した永続側の設定を上書きしない。
  • Dockerfile 側の初期化はそのまま残した(イメージが単体でも正しい既定を持つ状態を維持するため)。
  • テスト 3 件を追加(tests/containers/test_entrypoint_ai_settings.py): 初回起動で hooks が残る / 永続側の既存設定を上書きしない / 2 回目は symlink 越しにシードしない。
  • issues/PLAN39_account-group-volume-separation.md の AC6 にこの前提を追記。

スコープ外として扱ったもの

なし(今回のスイープでは gcloud/gws・snapshot/status 関連の未解決指摘は残っていませんでした)。

残 open thread: 0 件

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant