에이전트 코딩 환경 — 워크트리·포트·자격증명

언제 — 워크트리를 여럿 띄웠는데 서로의 서버를 죽이거나, 의존성 검증이 거짓 통과하거나, 새 워크트리에서 첫 명령이 실패할 때.

질문 아니오 예
24시간 켜진 별도 호스트가 있나 R8 폐기 R8까지
폰·외부 머신에서 세션을 이어받나 R4를 localhost 포트 격리로 축소 R4를 프록시 + tailnet URL로
네이티브 API(Vision·Swift 바인딩)에 의존하나 R5의 컨테이너 격리 가능 컨테이너 불가 — 호스트 직접 실행 + 토큰 축소로만

실행 순서: R6 dotfiles 리포(나머지 전부의 저장소) → R5-1 토큰 축소(30분·1회·효과 최대, 격리 설계를 기다릴 이유가 없다) → R1 워크트리 위치(훅 하나) → R2·R3(R1 의 setup() 채우기) → R7 설계 결정 → R4 프리뷰 서버(외부 접근이면 프록시) → R5-2 실행 격리(네이티브 의존이면 컨테이너 불가) → R8 원격 호스트(24시간 호스트가 있을 때만). 각 단계가 독립적으로 가치를 내므로 중간에 멈춰도 된다. 프로젝트 종류가 섞이면 격리 정책도 프로젝트별로 가른다 — 단일 정책은 가장 약한 쪽으로 수렴한다. 유통기한: R1 의 절반은 언젠가 설정 한 줄로 대체되지만 R2·R3·R5·R7 은 워크플로 설계 문제라 대체되지 않는다.

R1. 워크트리를 프로젝트 밖으로

기본 위치 <project>/.claude/worktrees/<name> 은 프로젝트 안이라, 워크트리에서 node_modules 해석이 상위 프로젝트까지 올라가 의존성 삭제·다운그레이드 검증이 거짓 통과하고, tsc project references·vitest glob·rg·find 같은 재귀 도구가 전부 워크트리로 들어간다. AGENTS.md 에 적는 건 확률적 층이라 WorktreeCreate·WorktreeRemove 훅으로 내린다 — 이 훅은 기본 git 동작을 증강이 아니라 대체한다. 훅은 ~/.worktrees/<project>/<name> 에 만든다. stdout 은 경로 한 줄 전용이라 git worktree add 를 포함한 다른 출력은 전부 >&2 로 보내고, 비영 종료는 생성 중단이며 JSON 반환도 못 한다. 훅은 settings.local.json 이 아니라 settings.json 에 둔다.

확인 — 훅에 WorktreeCreate 입력을 직접 넣으면 경로 한 줄만 나온다. 그 워크트리에서 의존성을 resolve 하면 워크트리 안 경로가 나온다 — 밖이면 격리가 안 된 것이다.

R2. 워크트리별 결정적 포트

브랜치(워크트리) 이름을 shasum 으로 해시해 숫자만 6자리 뽑고, 6000 으로 나눈 나머지에 3100 을 더해 PORT 로 .env.local 에 쓴다. 같은 브랜치는 항상 같은 포트라 재현되고 URL 을 기억할 수 있다. macOS 엔 md5sum 이 없으니 shasum·cksum 을 쓴다. 10# 은 선행 0 을 8진수로 읽어 간헐적으로 죽는 걸 막는다. 해시 충돌은 드물지만 결정적이라 한 번 나면 그 브랜치쌍은 영원히 충돌한다.

R3. gitignore 된 파일 승계

새 워크트리엔 .env·로컬 인증서·빌드 캐시가 없어 에이전트가 첫 명령에서 실패하고 원인을 추측하다 세션 절반을 태운다. 승계 목록을 리포의 .worktreeinclude 에 선언하고 훅이 rsync 로 복사한 뒤 pnpm install 한다. 심링크는 금지 — 워크트리에서 값을 바꾸면 메인과 다른 워크트리 전부가 동시에 오염된다. 승계 목록이 곧 유출 경로라 자격증명을 넣으면 워크트리 개수만큼 사본이 늘므로 WorktreeRemove 에서 확실히 지운다. 설치가 오래 걸리면 훅 timeout 을 늘린다 — 타임아웃은 생성 실패다.

R4. 프리뷰 서버 규약

에이전트가 주는 URL 은 실제로 열려야 하고 secure context 여야 한다(crypto.subtle·클립보드·서비스 워커). 로컬 전용이면 http://localhost:<port> 가 이미 secure context 라 R2 포트만 있으면 되고, AGENTS.md 에 .env.local 의 PORT 를 쓰고 점유돼 있으면 죽이지 말고 보고하라고만 적는다. 외부 접근이면 0.0.0.0 바인딩 + IP 접근은 secure context 가 아니다. 프로젝트 dev 스크립트를 고치지 말고(환경 관례가 리포에 침투한다) 래퍼 바이너리를 만들어 하네스가 부르게 한다. 이 규약은 확률적 층이라 결정적으로 하려면 PreToolUse 훅에서 dev 명령을 잡아 차단하거나 래퍼로 치환한다.

R5. 자격증명 축소가 먼저다

VM 으로 파일시스템 경계를 그어도 그 안에 전권 토큰을 넣으면 토큰은 계정 전체에 닿는다 — 실제 폭발 반경은 VM 이 아니라 계정이다. 위협은 사고만이 아니라 prompt injection 이다: 이슈 본문·PR 코멘트·의존성 README 를 읽는 순간 외부 텍스트가 명령이 된다. 순서: 1 토큰 축소(30분·1회) — fine-grained PAT, 대상 리포만, Contents·PR·Issues 만 read/write, Workflows 와 Administration 제외, 만료일. 2 실행 격리 — 순수 웹/Node 만 devcontainer 에서 bypassPermissions, 네이티브 의존은 컨테이너 불가라 기본 권한. 3 주입 표면 좁히기 — 신뢰할 수 없는 텍스트를 읽는 세션과 쓰기 권한 세션을 나눈다. 컨테이너를 미룰 때 비용이 거의 없는 토큰 축소까지 미루지 않는다.

확인 — gh auth status 로 스코프를 본다.

R6·R7. 전역 설정과 설계 결정을 세션 밖으로

R6: settings.json·훅·스킬·AGENTS.md 가 홈에 흩어져 이력도 백업도 없다. private 리포 하나 + 심링크로 끝난다 — 머신이 하나면 chezmoi 는 과잉이지만 이력과 백업은 머신 수와 무관하다. 하네스가 파일을 rename 으로 대체하면 심링크가 깨지니 ls -l 로 확인하고, .gitignore 에 .local.json·.env·credentials 를 넣고 첫 커밋 전 git diff –cached 를 훑는다. R7: 트랜스크립트가 설계 결정의 유일한 기록일 때 세션 히스토리 백업은 틀린 프레이밍이다 — 신호 대 잡음비가 최악이라 참조 대상이 안 된다. 기각한 대안이 있었을 때만 코드와 같은 커밋에 docs/decisions/NNNN-slug.md 를 추가한다: 맥락·결정·기각한 대안과 이유·결과, 한 화면 이내, 결론만.

확인 — 나중에 “왜 이렇게 했지?“를 물었을 때 리포 안에서 답이 나오면 성공. git log 와 트랜스크립트를 뒤져야 하면 실패.

R8. 원격 호스트 (조건부)

24시간 켜진 호스트가 있으면 Tailscale 로 SSH·HTTPS 를 호스트명으로 연다. 포트포워딩 대신 이걸 택하는 이유는 HTTPS 인증서로 secure context 를 얻기 때문이다. 랩탑을 닫아도 지속되려면 하네스의 원격 세션 모드를 쓰고 tmux 는 최후 수단이다. 머신이 둘이 되니 chezmoi 가 정당화된다. 다만 워크트리·포트를 아무리 격리해도 병렬화의 상한은 사람의 diff 리뷰 대역폭이다 — 4개 동시 실행은 4개의 리뷰 큐를 만들 뿐이니 병렬도를 올리기 전에 리뷰 처리량을 먼저 잰다.

#583
# /home/USERNAME/.zshrc

HOME="/mnt/c/Users/cbcru"
DL="$HOME/Downloads"

if [[ $PWD == $HOME ]]; then
    cd $DL
fi
#75