# 구현 계획서 (Refined v2): 신규 격리 에이전트의 /login·TOS/테마 프롬프트 근본 원인 및 조치 > 본 문서는 Creator의 Challenge Report(대상: Job 4c9ca21f / 본 계획서의 v1)를 반영해 정교화한 버전이다. v1 대비 변경점은 0장에 요약한다. ## 0. Challenge 반영 변경 이력 (v1 → v2) | # | Challenge 지적 사항 | 판정 | v2 조치 | |---|---|---|---| | 1 | 심볼릭 링크 기반 시딩은 mutable 디렉터리/preference에 대해 쓰기 격리를 제공하지 못해, 한 세션의 변경이 호스트와 다른 동시 세션에 즉시 전파됨 (race/상태 오염) | **타당함, 수용** | 3장을 "쓰기 격리 재설계"로 전면 확장. 대상을 위험도별로 분리해 단일-정본 preference/state 파일은 `cp`(1회 복사)로, append형 대용량 데이터 디렉터리는 현행 유지+후속 논의 항목으로 분리 | | 2 | `claude`는 `HOME`이 리디렉션되지 않고 `CLAUDE_CONFIG_DIR`만 바뀌므로, macOS Keychain 조회는 항상 실제 `$HOME`을 사용 → `$root/Library/Keychains` 심링크는 `claude`에 한해 아무 효과가 없는 죽은 코드 | **타당함, 수용** | 2.1의 "3중 원인" 서술에서 Keychain 항목 제거, root cause를 2개로 정정. 3장 표에서 `claude`의 Keychain 시딩 항목을 "제거 대상(dead code)"으로 변경. `agy`는 lever가 `home`이라 Keychain 시딩이 실질적으로 유효함을 명시적으로 구분 | ## 1. 목표 새로 생성된 `claude` 격리 세션이 `/login`을 요구하고, `agy` 격리 세션이 TOS/테마 선택 화면을 띄우는 문제의 근본 원인을 분석하고, **동시에 이 과정에서 세션 간 쓰기 격리(write-isolation)를 훼손하지 않도록** `.agents/skills/lib.sh`의 `provision_isolation()`을 재설계한다. ## 2. 근본 원인 분석 (Root Cause Analysis) — 정정판 ### 2.1 `claude` — `/login` 프롬프트 (정정: 원인은 2개) `claude`의 격리 lever는 `claude_config_dir`(`CLAUDE_CONFIG_DIR=$root`)이며, **`HOME`은 리디렉션되지 않는다** (`isolation_env_prefix()`, lib.sh:1318-1325 — `claude` 분기는 `CLAUDE_CONFIG_DIR`만 설정). 이 사실이 원인 분석의 핵심 제약이다. - **원인 A — `session-env`/`sessions`/`cache` 미시딩**: 이 세 디렉터리는 `$root` 바로 아래(`$root/session-env` 등)에 위치하며, 이는 `CLAUDE_CONFIG_DIR`가 지배하는 네임스페이스에 정확히 속한다. 기존 코드는 이 세 항목을 전혀 시딩하지 않았고, CLI는 격리된 `CLAUDE_CONFIG_DIR`를 "낯선 세션"으로 인식해 로그인 플로우를 반복 요구했다. - **원인 B — `.credentials.json` 무조건 링크 버그**: `ln -sfn "$HOME/.claude/.credentials.json" ...`이 존재 확인 없이 실행되어, 이 파일이 없는 환경(실측: 이 머신에서 `.credentials.json`은 부재)에서 깨진 심볼릭 링크를 생성하는 상태 불일치 버그. - **~~Keychain 시딩~~ (v1에서 원인으로 지목했으나 정정)**: `claude` 프로세스는 `HOME`이 그대로이므로 macOS `security`/Security.framework 조회는 항상 실제 `$HOME/Library/Keychains`를 향한다. `$root/Library/Keychains` 심링크는 `CLAUDE_CONFIG_DIR` 네임스페이스 밖에 있어 `claude` 프로세스의 어떤 조회 경로도 거치지 않는다 — **효과 없는 죽은 코드**이며 `/login` 프롬프트 해소에 기여하지 않았다. **결론(정정)**: `/login` 반복 프롬프트의 실제 원인은 (a) `session-env`/`sessions`/`cache` 미시딩, (b) 존재하지 않는 credentials 파일에 대한 무조건적 링크 생성, 2가지다. Keychain 시딩은 `claude` 케이스에서는 무관한 항목이었다. ### 2.2 `agy` — TOS/테마 선택 프롬프트 (변경 없음, Keychain 유효성 근거 보강) `agy`의 lever는 `home`이며, `isolation_env_prefix()`가 `HOME=$root`를 설정해 **`HOME`이 실제로 리디렉션**된다. 따라서 `agy`에서는 `$root/Library/Keychains` 심링크가 실제 Keychain 조회 경로 위에 있어 유효하다 — 이는 `claude`와 정확히 대비되는 지점이며, Challenge #2가 "claude에 한해" 지적한 것과 일치한다. Antigravity는 CLI(`~/.gemini/antigravity-cli`, 기시딩)와 **IDE**(`~/.gemini/antigravity-ide`, `com.google.antigravity-ide.plist`, `~/Library/Application Support/Antigravity IDE`)로 나뉜다. 기존 코드가 IDE 전용 상태 저장소(TOS 동의/테마)를 시딩 대상에서 누락한 것이 근본 원인이며, 이 결론은 v1과 동일하게 유지된다. ## 3. 구현 계획 — 쓰기 격리 재설계 (Write-Isolation Redesign) Challenge #1의 핵심은: **디렉터리를 `ln -sfn`으로 연결하면, 그 안에 새로 생성되는 파일/갱신되는 값이 실제로는 호스트의 원본 디렉터리에 그대로 쓰여지고, 동시에 실행 중인 다른 격리 세션에도 즉시 보인다**는 점이다. 이를 단일 정책(전부 `cp -R`)으로 일괄 해결하기보다, 항목별 위험도에 따라 차등 전략을 적용한다. ### 3.1 위험도 분류 및 전략 | 위험도 | 대상 | 문제 유형 | 전략 | |---|---|---|---| | **높음** — 단일 정본 preference (theme/TOS 동의 플래그 등 전역 상태 1개 값을 담음) | macOS `Library/Preferences/*.plist` (`com.google.antigravity*.plist`, `com.google.GeminiMacOS*.plist`), `agy`의 `~/.gemini/antigravity-ide`(설정 JSON), `claude`의 `settings.json` | 한 세션의 테마/설정 변경이 즉시 호스트 및 다른 모든 동시 세션의 동작을 바꿔버림 — 실사용자 관점의 명백한 버그 | **`ln -sfn` → `cp -a` (프로비저닝 시점 1회 복사, 대상이 root에 이미 없을 때만)**로 전환. 세션별 독립 사본을 갖되, 최초 부팅 상태는 호스트의 기시딩(온보딩 완료) 상태를 그대로 물려받음 | | **중간** — append형 대용량/이력 디렉터리 | `~/.claude/session-env`, `sessions`, `cache`; `agy`의 `conversation_summaries.db`, `jetski_state.pbtxt` | 세션마다 별도 키(세션 ID 등)로 항목이 추가되는 구조로 보이며, 격리 없이도 실제 사용자가 한 머신에서 여러 터미널을 동시에 쓸 때 이미 공유되는 것과 동일한 패턴 | **현행 유지 (심링크)**. 단, 이는 "허용된 기존 동작과의 동등성"에 근거한 잠정 결론이며, 실제로 세션별 격리가 제품 요구사항인지는 4.3의 후속 논의 항목으로 남김 | | **해당 없음(claude)/유효(agy)** — Keychain | `Library/Keychains` | `claude`: 원인 무관 죽은 코드 / `agy`: `HOME` 리디렉션으로 실제 유효 | **`claude` 분기에서 `Library/Keychains` 시딩 블록 제거**(dead code 정리). **`agy` 분기는 유지** — Keychain 자체는 원본 파일을 직접 열람 가능해야 잠금해제/ACL이 성립하므로 복사 대상에서 제외하고 심링크 유지가 맞음 | | **낮음** — 순수 식별자/자격 증명 (거의 재기록되지 않고, 재기록 시 명시적 `/login` 흐름을 통해서만 발생) | `.claude.json`, `.credentials.json`, `.gemini/*` 의 `oauth_creds.json`/`installation_id`/`antigravity-oauth-token` 등 | 에이전트 프로세스 자체가 실행 중 이 파일을 능동적으로 재작성하는 경로가 없음(있다면 그것은 곧 재로그인이 필요하다는 신호이므로 오히려 격리가 무의미) | **현행 유지 (심링크)** | ### 3.2 갱신된 구현 표 | 대상 | 항목 | 방식 | 비고 | |---|---|---|---| | `claude` | `session-env`, `sessions`, `cache` | 심링크 유지 | 3.1 "중간" 위험도, 현행 유지 | | `claude` | `settings.json` | **`cp -a` 1회 복사로 전환** | 3.1 "높음" | | `claude` | `.credentials.json`, `.claude.json`, `plugins` | 심링크 유지 + `[ -e ... ]` 가드 추가(v1 그대로) | 3.1 "낮음" | | `claude` | `Library/Keychains` | **시딩 블록 제거** | Challenge #2 반영, dead code | | `agy` | `antigravity-ide` 설정 디렉터리, `Library/Preferences/*.plist`, `Library/Application Support/Antigravity*`, `com.google.GeminiMacOS*` | **`cp -a` 1회 복사로 전환** | 3.1 "높음" — TOS/테마 상태가 이 경로들에 있음 | | `agy` | `.gemini/antigravity-cli/*`, `conversation_summaries.db`, `jetski_state.pbtxt` | 심링크 유지 | 3.1 "중간" | | `agy` | `Library/Keychains` | 심링크 유지 | 3.1 "유효(agy)" | | `agy` | `oauth_creds.json`, `google_accounts.json`, `installation_id` 등 자격 증명 | 심링크 유지 | 3.1 "낮음" | | 공통 | `seeded` 누적 가드 | 모든 대입에 `${seeded:+$seeded,}` 일관 적용 | v1과 동일, 변경 없음 | `cp -a`로 전환하는 항목은 반드시 **"대상이 `$root`에 이미 존재하지 않을 때만 복사"** 조건을 걸어, 동일 격리 root를 재사용하는 세션 재시작 시 이전 세션에서 쌓인 로컬 변경(테마 등)을 매번 덮어쓰지 않도록 한다 (`[ -e "$root/..." ] || cp -a "$HOME/..." "$root/..."`). ## 4. 검증 (Verification) ### 4.1 v1 검증 결과 재확인 (변경 없음) - `bash -n .agents/skills/lib.sh` → 통과 (v1과 동일한 워킹 트리 diff, 변경 없음 확인: `git diff --stat` 여전히 `.agents/skills/lib.sh | 32 insertions(+), 10 deletions(-)`). - `git status --short` → 추적 파일 변경은 `.agents/skills/lib.sh` 하나뿐 (미추적 `.tmp`/`.DS_Store`는 리뷰 대상 아님, v1과 동일). - 실제 파일시스템 대조 결과(v1의 4.3)는 그대로 유효. ### 4.2 본 v2 계획과 "현재 워킹 트리 diff"의 관계 **중요**: 현재 워킹 트리에 반영된 diff는 v1 계획(심링크 전면 적용)과 일치하는 상태이며, **본 v2에서 새로 제안한 `cp -a` 전환 및 `claude` Keychain 제거는 아직 코드에 반영되어 있지 않다.** 즉 v2는 v1 diff에 대한 "PASS 재확인"이 아니라, **추가 구현이 필요한 차기 변경 제안**이다. 따라서 이번 라운드는 계획 문서 갱신에 한정하고, 코드 반영은 별도 Implementer 단계로 넘긴다 (본 Job의 역할은 Planner이며 "직접 코드를 수정하지 말라"는 종전 리뷰 라운드들의 제약과 일관되게, 이번에도 `.agents/skills/lib.sh`에 대한 실제 편집은 수행하지 않았다). ### 4.3 후속 논의가 필요한 열린 질문 - `session-env`/`sessions`/`cache`(그리고 `agy`의 `conversation_summaries.db` 등)를 "중간" 위험도로 분류해 현행 심링크를 유지하기로 했으나, 이는 "실제 제품 요구사항이 세션별 완전 격리인지, 아니면 호스트와의 이력 공유가 의도된 동작인지"에 대한 확인 없이 잠정 판단한 것이다. Creator/제품 오너 확인 후 필요시 이 항목도 3.1 "높음"으로 재분류해 `cp -a`로 전환해야 할 수 있다. ## 5. 결론 Creator의 Challenge 2건은 모두 코드/아키텍처 사실에 부합하는 타당한 지적으로 확인되어 계획에 반영했다: (1) mutable 단일-정본 preference/state에 대한 쓰기 격리 부재는 위험도 기반 `cp -a` 전환으로, (2) `claude`의 Keychain 시딩 무효성은 해당 블록 제거로 각각 대응한다. 현재 워킹 트리 diff는 여전히 v1 설계를 반영한 상태이며 문법/파일시스템 검증은 기존과 동일하게 통과하지만, 본 v2에서 제안한 `cp -a` 전환 및 Keychain 제거는 아직 미구현 상태로, 차기 Implementer 라운드에서 반영이 필요하다. **Unanimous PASS (계획 문서 갱신 완료, 코드 반영은 후속 라운드 필요)** [VERDICT: PASS]