コンテンツにスキップ

ADR-0004: 作業枠の成果物を「消えない場所」へ昇格する

  • 状態: Accepted(未決を 2 つ残す)
  • 日付: 2026-08-31
  • 関連: #63、ADR-0001、ADR-0002、docs/design.md §4.4 / §10

文脈

試作が消えた。 private な Web 実装(コミット 4 本・約 1,500 行)が 5 日後に無くなった。 ローカル git で管理していたので安全だと判断していたが、置き場所が悪かった(#63)。

「tmp」が 2 つあり、寿命が違う

パス 寿命
ccs の作業枠 ~/.cc-scratch/<workspaceId> ホーム配下。OS は消さない
ハーネスの scratchpad /private/tmp/claude-…/…/scratchpad/ /tmp 配下。macOS が数日で消す

Claude Code のシステムプロンプトは一時ファイルを後者へ置くよう指示する。ccs new --tmp の セッションは cwd が前者になるが、「一時的な作業」と言われたエージェントは後者を選ぶ。

作業枠は消えないのに、作業枠に立てたセッションの成果物が消える。 ここが穴。

欲しい 3 段階

レベル 置き場所 保証
tmp ~/.cc-scratch/<workspaceId> リセットで消えてよい
local ~/ghq/local/<owner>/<name> ディレクトリは消さない。remote は無い
remote ~/ghq/github.com/<owner>/<name> GitHub にも残る

実測(2026-08-31)

この 2 つが設計を決めた。

1. claude --resume <uuid> は cwd を跨げる

作業枠で 1 往復した会話を畳み、別のディレクトリ(~/ghq/local/ken-ty/…)へ中身を移して claude --resume <uuid> を打ったところ、会話がそのまま復元された。セッション名も残る。

→ 昇格しても同じ会話を続けられる。 セッションの張り替えは「立て直す」だけでよく、 restore_launch と同じ手が使える。

2. 会話ログは古い場所に残る

同じ実測で、レジストリの cwd は新しい場所になったが、会話ログは元の ~/.claude/projects/<エンコードした古い cwd>/ に残ったままだった。

→ ccs restore の索引は古い枠を指し続ける。 昇格したセッションが「空になった(あるいは 消えた)枠に戻す候補」として並ぶ。ここを手当てしないと昇格は完成しない(→ 未決 1)。

決定

1. 根治を先に置く。昇格は事後の救済である

穴の本体は「エージェントが /tmp を選ぶこと」であって、成果物が枠に残らないこと。 昇格コマンドは既に散らばったものを拾う道具で、次に同じことが起きるのは止められない。

作業枠のセッションに「成果物は cwd に置く」と伝える。 hub が CLAUDE.md を置いている ので同じ手が使える、と当初は考えた。

これは昇格より小さく、先に効く。 別項目として先に片付ける。

見直し(2026-08-31、P0 の実装時): CLAUDE.md は置かない。--append-system-prompt で渡す。

hub との類推が成り立たなかった。 hub の作業ディレクトリは自動承認の条件から外れて いる(path_is_hub_home は「中身が空である必要は無い」と明記)が、作業枠は「印以外に 何も無い」が自動承認の条件そのもの(決定 4、I2c)。枠に CLAUDE.md を置くと:

  • 自動承認が止まる(I2c の条件 3 を破る)
  • ccs gc が掃除できなくなる(I3a の「印だけ」に落ちず、「印と中身」=報告だけになる)

逃げ道として「ccs が置いた CLAUDE.md は数えない」も考えたが、利用者がそこに書き足した ときに見分けが付かない ── 中身を見ないと判断できず、見たとしても「消してよい」と決める 根拠にはならない。外れたら消す側に倒れるので採らない。

claude --append-system-prompt <text> ならファイルを置かずに伝わるので、どの不変条件も 壊さない。文面は CCS_SCRATCH_NOTE で変えられ、空にすれば渡さない。

2. 移す先は ~/ghq/local/<owner>/<name>

  • 既にその形で使われている(実測: ~/ghq/local/ken-ty/goita-proto、remote 無しの git リポジトリ)
  • ghq list は local/ 配下を深さを問わず拾う(#63 の実測、ghq 1.7.1)
  • ghq create は宛先に github.com/ を前置するので使えない。代わりは mkdir -p && git init の 2 行しかなく、ghq 本体に手を入れる価値は無い

3. ccs は移送先を git リポジトリにする

~/ghq/local/… に置くだけでは ghq list に出ない(ghq は .git を見る)。「local に昇格した」が 意味を持つのは git リポジトリになったときだけなので、git init はここに含める。

新規ディレクトリに対する git init は破壊的ではないので、ccs の後片付けの線 (git/rmdir が安全と認めたものだけ触る)を越えない。

枠が既に git リポジトリなら .git ごと移す。 履歴を捨てない。

  • 空になれば ccs gc の対象に戻る(I3a の「印だけ」=消す)
  • symlink は残さない ── 枠は捨てる前提の場所なので、移送先が消えたときに 壊れたリンクだけが残る。「枠を見れば分かる」は印が担う役割で、symlink の仕事ではない

5. セッションは立て直す

tmux のペインの cwd は後から変えられない。実測 1 のとおり --resume は cwd を跨げるので、 ccs adopt と同じ形(元を閉じる → 新しい cwd で --resume)で足りる。

新しい場所は ghq 配下なので ensure_trust が自動承認する(design.md §4.4)。実測でも ccs を通さずに立てたときだけ信頼確認が出た。

6. remote 段階(gh repo create)は ccs がやらない

外部への送信は人の承認が要る(AGENTS.md)。ccs promote remote は打つコマンドを出すだけに する ── ccs gc が消す候補を予告するのと同じ形。

ccs の 4 責務に「外部サービスにリポジトリを作る」は無い。ここを持つと 5 つ目になる。

未決(人の判断が要る)

1. 昇格した会話を ccs restore がどう扱うか

実測 2 のとおり、会話ログは古い枠の索引に残る。昇格して枠を空にしたあと、 ccs restore はその会話を「枠に戻す候補」として並べてしまう。

案 中身 気になる点
A 昇格時に枠の印へ promotedTo を書き、restore がそれを見て skip 枠を gc が消すと印も消えるので、そのあとは効かない
B restore_reject_reason の cwd 突き合わせに任せる 会話ログの cwd が更新されるかが未検証。先に実測が要る
C 昇格時に会話ログのディレクトリごと新しい索引名へ移す Claude の内部の置き場所を ccs が書き換えることになる。いままで避けてきた

B を先に実測すべきだと考えている。もし会話ログの cwd が新しい場所に更新されるなら、 既存の検査だけで弾けて、新しい仕組みが要らない。

2. ~/ghq/local/ が別用途で使われている環境との同居

素材置き場(git 管理外)として使われていることがありうる(#63)。ccs が local/<owner>/<name> を掘るとき、既存の中身とどう同居させるかは決めていない。

影響

  • docs/design.md §4.4(作業枠)に「昇格」を足す
  • ROADMAP に、根治(決定 1)を昇格より前の項目として立てる
  • ccs promote は P1b で実装する。未決 1 が決まってから