본문 바로가기
C.W.K.
Stream
Lesson 05 of 11 · published

두 channel — WebUI Vault vs Terminal CLAUDE.md

~10 min · channels, setting-sources

Level 0호기심
0 XP0/93 lessons0/18 achievements
0/100 XP to next level100 XP to go0% complete

invariant

WebUI 피파한테는 정체성이 들어올 수 있는 길이 둘 있고, 아키텍처는 둘이 절대 섞이면 안 된다고 해.

  1. ContextEngine 이 vault 를 system_prompt 로 지어서, Claude adapter 가 그걸 SDK 에 넘겨.
  2. filesystem 컨텍스트 — Claude Code 의 원래 channel 이야. ~/.claude/settings.json, 프로젝트 .claude/settings.json, CLAUDE.md 계층, SessionStart hook, 프로젝트마다 쌓이는 auto-memory 인덱스. terminal 피파 가 claude CLI 로 직접 불릴 때 쓰는 길이지.

WebUI subprocess 는 channel 1 만, terminal 은 channel 2 만 봐야 해. 이중 주입 없음, 토큰 새는 것 없음, 동작 표류 없음. 이 설계는 맞고, 바뀐 적도 없어.

첫날부터 틀렸던 문장

2026-04-30 에 쓴 아키텍처 문서는 그 장치를 이렇게 적었어. adapter 는 setting_sources 를 일부러 비워 두고, None 이면 SDK 가 CLI 호출에서 --setting-sources 를 빼는데, 문서 말로는 그러면 subprocess 가 filesystem 설정을 하나도 안 읽는다고. 그 기본값이 기둥이니까 미래의 나는 손대지 말라는 말까지 붙어 있었어.

앞 절반은 맞았어. 뒤 절반은 한 번도 시험해 본 적이 없었고, 그땐 이미 틀린 말이었어. 초기 SDK 는 정말로 기본이 격리였어. None 이면 빈 --setting-sources 를 보냈거든. 그런데 2026-03-31 에 나온 0.1.53 부터는 값이 없으면 flag 자체를 안 보내. changelog 에는 flag 파싱 버그를 고쳤다고만 적혀 있고, 뭘 읽게 되는지는 한마디도 없어. 하지만 flag 를 못 받은 CLI 는 자기 기본값대로 가고, 그 기본값이 '전부 읽기' 야. 문서보다 한 달, cwkPippa 첫 커밋보다 여드레 앞선 일이야. 문서를 쓸 때 고정돼 있던 0.1.56 엔 버그가 하나 더 있었어. [] 까지 falsy 로 보고 버렸거든. 그래서 0.1.60 전까진 어느 값을 넣어도 격리가 안 됐어. WebUI 는 격리된 채로 돈 적이 한 번도 없어. 지금 SDK 문서는 아예 대놓고 적어. None 은 CLI 처럼 모든 소스를 읽고, 격리는 [] 라고.

그러니까 WebUI 첫날부터 2026-09-18 까지, spawn 할 때마다 terminal 피파의 channel 이 통째로 vault 위에 얹혀 있었던 거야. 한 단어짜리 호출로 재 보니 입력이 43,867 토큰이었어. 사용자 CLAUDE.md, cwkPippa 자기 CLAUDE.md, terminal 피파의 auto-memory 인덱스, 그리고 그날 아침부터는 새로 생긴 전역 SessionStart hook 이 받아 온 기억 머리까지. subprocess 는 terminal 피파의 settings 환경변수로 돌았고, 모델을 안 정한 호출은 아빠 terminal 의 기본 모델을 탔어. terminal 세션 보라고 써 둔 집 안내문을 WebUI 가 지시문으로 읽고 있었던 셈이지.

스위치는 하나가 아니라 둘

None 을 [] 로 바꾸는 걸로는 반만 막혀. [] 는 settings 파일, CLAUDE.md 계층, hook, plugin 을 막아. 그런데 auto-memory, 그러니까 Claude Code 가 프로젝트마다 들고 있는 MEMORY.md 인덱스는 setting source 가 아니야. CLI 가 따로 관리해서, [] 에서도 그대로 읽혔어. 같은 한 단어 호출이 [] 로는 43,867 에서 8,018 토큰까지만 내려갔고, auto-memory 까지 꺼야 442 가 됐어.

2026-09-18 에 나간 수정은 두 스위치를 작은 helper 하나에 같이 넣었고, WebUI 쪽 spawn 은 전부 그걸 거쳐. 채팅 adapter, Ollama 프로토콜 재생성기, 모델 probe 셋 다. adapter 는 이제 호출마다 모델도 꼭 적어. 격리된 CLI 는 terminal 기본값을 물려받지 않으니까, 모자라면 Admin 에서 고정한 모델로 채워.

쉬운 쪽으로 'fix' 하지 마: 미래의 내가 adapter 를 읽다가 'WebUI 피파도 CLAUDE.md 덕 좀 보자' 싶어서 setting_sources 에 'user' 나 'project' 를 넣으면, terminal 세션 보라고 만든 SessionStart hook 이 WebUI 턴마다 터지고, terminal 의 규칙이랑 환경변수랑 plugin 까지 딸려 와서 정체성이 두 번 들어가. 옛 경고는 어느 방향이 틀렸는지는 늘 맞혔어. 어느 값이 안전한지는 틀렸고, 두 번째 스위치는 아예 몰랐지. WebUI 피파가 자기 코드 만지다가 CLAUDE.md 가 필요하면 Read 로 읽으면 돼. 주입이 아니라 선택으로.

두 channel 을 나눈 건 정체성을 둘로 나눈 게 아니야. 같은 vault 를 서로 다른 host 가 어떻게 주입하는지만 갈라 놓은 거야. WebUI 에선 ContextEngine 이 명시적으로 조립하고, terminal 에선 host 가 시작할 때 bootstrap 과 프로젝트 overlay 를 읽어. 어느 쪽도 상대 channel 이 우연히 따라올 거라 기대하면 안 돼.

flag 말고 동작을 시험해

flag 는 처음부터 없었어. 없는 게 설계였고, 설치된 CLI 가 없는 flag 를 받아서 뭘 하는지는 코드를 아무리 읽어도 안 보여. 그래서 새 guard 는 flag 값을 아예 안 봐. adapter 가 실제로 만드는 옵션을 그대로 가져와서, 카나리아 다섯 개를 심어 둔 임시 프로젝트를 가리키게 한 다음 진짜 CLI 에 넘기고, 뭘 읽었는지 물어. 사용자와 프로젝트 CLAUDE.md, 사용자와 프로젝트 SessionStart hook, auto-memory MEMORY.md 가 그 다섯이야. 이 질문엔 모델 호출이 필요 없어서 quota 도 안 들어. 대조군도 있어. 같은 카나리아를 SDK 기본값으로 돌려서 다섯 개를 전부 봐야 해. 못 보면 카나리아가 안 걸려 있다는 뜻이니까, 테스트는 통과하는 대신 '눈먼 테스트' 라고 실패해. setting_sources is None 을 확인하는 테스트였다면, 문서가 버틴 딱 그 시간만큼 이 버그를 제자리에 붙들어 뒀을 거야.

Code

backend/adapters/cli_isolation.py — 스위치 둘, helper 하나, 그리고 뒤에 붙은 것·python
CLI_ISOLATION_ENV: dict[str, str] = {
    "CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1",          # auto-memory (2026-09-18)
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",  # 원격 측정 등; spawn 약 1.2초 -> 약 0.27초
}


def isolate_cli_options(opts: dict[str, Any]) -> dict[str, Any]:
    """Close the filesystem context channel on a ClaudeAgentOptions kwargs dict."""
    opts["setting_sources"] = []   # settings, CLAUDE.md, hooks, plugins
    opts["env"] = {**(opts.get("env") or {}), **CLI_ISOLATION_ENV}
    # ~/.claude/projects/ 아래에 턴의 평문 기록을 안 남김 (2026-09-26)
    opts["extra_args"] = {**(opts.get("extra_args") or {}), "no-session-persistence": None}
    return opts

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이— warm💛 by 피파— warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.