문제는 이것이었다. 내가 Claude Code(터미널에서 쓰는 Anthropic의 AI 코딩 에이전트) 위에 손수 지어 온 장치들이 있다. nohup·tmux로 띄우는 백그라운드 루프, 훅(hook — 정해진 순간마다 자동으로 실행되는 작은 스크립트)의 무한 재귀를 막는 자체 마커, 두 시간 뒤 바깥에서 세션을 죽이는 타이머 같은 것들이다. 그런 것들에 공식 대체가 있는지 모른 채 써 왔다. 그래서 code.claude.com 공식 레퍼런스(공식 참조 문서) 전 페이지, 곧 CLI · Commands · Env vars · Tools · Interactive mode · Checkpointing · Hooks · Plugins · Channels 아홉 쪽을 처음부터 끝까지 읽었다. 그리고 "어느 실행 모드에서 무엇이 되는가"라는 한 축으로 다시 정리했다. 결과는 첫 발견부터 정정이었다. Claude Code의 실행 모드는 흔히 2개로 알려져 있지만 실제로는 3개다.
결론부터 말하면 실행 모드는 셋이다. CLI reference(명령줄 옵션을 전부 적어 둔 공식 문서. 명령줄 인터페이스, 곧 터미널에 명령을 쳐서 쓰는 방식을 다룬다)를 정독하면 claude(대화형 — 사람이 터미널에서 대화하듯 쓰는 모드)와 claude -p(헤드리스 — 화면 없이 프롬프트, 즉 지시문 하나를 주고 결과만 받는 모드) 사이에 네이티브 백그라운드 세션 계층이 따로 있다. 이 계층은 --bg(세션을 뒤에서 띄우는 옵션) · claude agents(뒤에서 도는 세션들을 한눈에 보는 관제 명령) · attach/logs/respawn(붙기·로그 보기·다시 띄우기)으로 이루어진다. 하네스(harness — AI가 "다 했다"고 거짓말 못 하게 증거를 강제하는 감시 장치. 여기서는 내가 Claude Code 위에 얹어 둔 훅·스크립트 묶음)들이 nohup·tmux·lock으로 손수 짓던 백그라운드 루프의 공식 등가물이 바로 이것이다. 그리고 --bg와 -p는 함께 쓰는 것이 금지된 별개 모드다.
--bg와 -p는 함께 쓰는 것이 명시적으로 금지된다. 공식 CLI reference에서 실측한 내용이다.CLI Reference · Commands슬래시 명령(/로 시작하는 짧은 명령)은 모드마다 쓸 수 있는 범위가 다르다. 커스텀 명령(사용자가 직접 만든 슬래시 명령)은 이제 스킬로 통합됐다. 스킬(skill — 특정 작업의 절차를 적어 둔 지시 묶음)은 한 메시지에 최대 6개까지 이어 붙일 수 있다(체이닝). 슬래시 명령의 헤드리스 지원은 v2.1.205+(버전 2.1.205 이후)에서 "인자 직접 지정 + 픽커 없음" 형태만 열렸다. 픽커(picker)란 선택지를 화면에 띄워 사람이 고르게 하는 대화형 창이다.
| 축 | 대화형에서만 | 헤드리스(-p)에서도 |
|---|---|---|
| 슬래시 명령 | 픽커(선택창)·다이얼로그(대화창)형 전부: /rewind /resume /diff /plan /permissions |
/config key=value · /model <인자> · /effort · /mcp(텍스트 요약) · /color · /fast |
| 권한 처리 | 프롬프트 UI + Shift+Tab 모드 사이클 | --permission-prompt-tool(승인 질문을 MCP 서버에 넘겨 대신 답하게 하는 옵션. MCP는 Model Context Protocol, 외부 도구를 AI에 꽂는 표준 규약이다. 30s 대기) 또는 bypass(모든 승인을 건너뛰는 설정). 블랭킷 우회, 즉 bypass 하나로 전부 통과시키는 것이 유일한 길이 아니다 |
| 상한 | 없음 (사람이 중단) | --max-turns(대화 턴 수 상한) · --max-budget-usd(달러 예산 상한). 프로세스 내부 상한을 공식 지원한다 |
| 세션 영속 | 항상 저장 + 체크포인트 동반 | --no-session-persistence(세션 기록을 남기지 않는 옵션)로 무흔적 실행을 선택할 수 있다 |
| 구조화 출력 | — | --json-schema(답을 정해진 자료 틀에 맞춤) · --output-format stream-json(결과를 한 줄씩 기계가 읽는 형식으로 흘려보냄) · --include-hook-events(훅 실행 기록까지 출력에 포함) |
결론은 공식 권장 옵션이 이미 답이었다는 것이다. --exclude-dynamic-system-prompt-sections(시스템 프롬프트 가운데 세션마다 달라지는 동적 부분을 빼고 띄우는 옵션)는 문서에 "-p 스크립트·멀티유저 워크로드에 권장"으로 명시돼 있다. 이 세션에서 실측한 스폰 실패란 이런 것이다. 스폰(spawn — 세션이 다른 세션을 새로 띄우는 것)한 하위 세션이 시작하자마자 "Prompt is too long"(프롬프트가 너무 길다)이라며 죽었다. 원인은 부모 세션의 대형 동적 주입, 곧 자동으로 끼워 넣는 규칙과 컨텍스트(context — AI가 한 번에 읽고 있는 대화·문서 전체) 덩어리를 자식이 그대로 상속한 것이다. 그 공식 완화 수단이 이미 존재했다는 뜻이다.
환경 변수 두 개가 "내가 지금 어디서 도는가"를 알려준다. 하나는 Claude Code 아래에서 돌고 있다는 표시고, 다른 하나는 Claude Code가 나를 직접 띄웠다는 표시다. 둘은 뜻이 다르다. 중첩 세션(세션 안에서 띄운 세션)을 가려내는 데는 두 번째만 정확하다. 이어지는 표는 대화형과 헤드리스에서 API 키·백그라운드 태스크·환경 변수 우선순위·도구 권한이 어떻게 다른지 정리한 것이다.
CLAUDECODE=1"모든 하위 프로세스에 설정 — IDE 터미널 포함"
CLAUDE_CODE_CHILD_SESSION=1"중첩 세션의 공식 판별자"
| 사실 | 대화형 | 헤드리스(-p) |
|---|---|---|
| API 키 사용 | ANTHROPIC_API_KEY(API 키를 담는 환경 변수)를 쓰기 전 1회 승인 프롬프트가 뜬다 |
무언 사용, 즉 묻지 않고 쓴다. 구독 결제를 조용히 덮어쓴다 |
| 백그라운드 태스크 | 턴(한 번의 질문과 답)을 넘어 지속된다 (Ctrl+B·/tasks) |
최종 결과 직후 종료된다. 데몬(뒤에서 계속 도는 프로세스)을 남길 수 없는 구조다 |
| MCP 자동 백그라운딩 | 미적용 | CLAUDE_AUTO_BACKGROUND_TASKS(오래 걸리는 MCP 호출을 자동으로 뒤로 보내는 환경 변수)는 비대화형 전용이다 (v2.1.212+) |
| env 우선순위 | settings(설정 파일)의 env 블록이 셸 env(환경 변수)를 덮어쓴다. scope(설정이 적용되는 범위) 사이 우선순위는 managed > local > project > user 순이다. 그래서 워커(worker — 사람이 지켜보지 않는 채 뒤에서 일하는 헤드리스 세션)가 launchd(맥에서 백그라운드 작업을 띄우는 시스템 실행기)의 최소 env로 떠도 settings의 env는 똑같이 적용된다 |
|
| 도구 권한 (43종) | 내장 도구 43종 가운데 승인 프롬프트를 요구하는 것은 13종(Bash·Edit·Write·WebFetch·Skill·Workflow 등)이다. bypass가 없애는 것은 이 승인 프롬프트 계층뿐이다. 훅의 exit-2 게이트(gate — 훅이 종료 코드 2를 돌려주면 그 작업을 막는 관문)는 세 모드에서 똑같이 작동한다 |
|
되돌리기는 모드마다 구현이 다르다. 대화형은 세션 안의 체크포인트로 되돌리고, 헤드리스 워커는 세션 밖의 git으로 되돌린다. Interactive mode(대화형 모드) 페이지 전체가 사실상 "사람이 조향하는 표면"의 목록이다. Shift+Tab(권한 모드 전환), Ctrl+B 백그라운딩(작업을 뒤로 보내기), ! 셸 모드(터미널 명령 직접 실행), Esc Esc 되감기, /btw(본 작업을 멈추지 않고 곁가지 질문 하기), vim 모드(vim 편집기식 키 조작)가 그것이다. 전부 헤드리스 등가물이 없다. 그리고 Checkpointing(체크포인트 — 프롬프트마다 파일 상태를 자동 저장해 두는 기능) 페이지의 한계 목록이 두 모드의 "되돌리기" 구현 차이를 정확히 보여준다.
훅 설정은 여섯 곳에서 모여 합쳐진다. 훅은 user(내 계정)·project(저장소)·local(저장소 안 개인 설정)·managed(조직이 관리)·플러그인·스킬/에이전트 frontmatter(파일 머리에 적는 메타데이터) 6개 출처에서 병합된다. 나중 것이 앞 것을 대체하는 방식이 아니다. 동일 핸들러(같은 명령)는 dedup(중복 제거)되고, 매칭된 훅은 병렬 실행된다. disableAllHooks(모든 훅을 끄는 설정)로도 managed 훅은 못 끈다.
이벤트(훅이 걸리는 순간)는 세션 생명주기·턴·도구 호출·서브에이전트·압축·MCP·환경 변화·표시까지 30여 종이다. Setup은 헤드리스 전용 이벤트다(--init-only, 또는 -p+--init/--maintenance로 띄울 때만). 즉 CI(continuous integration — 코드를 올릴 때마다 자동으로 검사·빌드하는 서버) 준비용 이벤트가 따로 있다.
훅의 판정은 종료 코드(exit code — 프로그램이 끝나며 남기는 숫자)로만 전달된다. exit 1은 차단이 아니다(WorktreeCreate 이벤트만 예외다). 비차단 에러로 기록되고 작업은 계속 진행된다. exit 2일 때만 막히고, 그때 stderr(에러 출력)에 쓴 글만 Claude에게 전달된다. SessionEnd(세션 종료) 훅은 전부 합쳐 1.5초 공유 예산 안에 끝나야 한다.
훅은 서브에이전트(subagent — 본 세션이 일을 나눠 맡기려고 띄우는 하위 에이전트) 안에서도 작동한다. settings·managed·플러그인 훅이 그 안에서도 발화하고, 훅 입력에 agent_id/agent_type(어느 에이전트인지 알려주는 필드)이 추가된다. agent frontmatter에 적은 Stop(응답 종료 이벤트)은 SubagentStop으로 자동 변환된다.
플러그인(plugin — 스킬·에이전트·훅을 한 묶음으로 설치하는 확장 패키지)은 skills·agents·hooks·MCP에 더해 LSP(Language Server Protocol — 편집기에 자동완성·정의 이동을 제공하는 규약) 서버·모니터·테마까지 7종의 컴포넌트를 제공한다. 설치 스코프(설정이 적용되는 범위)는 셋이다. user 스코프는 내 계정 전체(~/.claude/settings.json)에, project 스코프는 저장소 하나(.claude/settings.json, 커밋해서 팀과 공유)에, managed 스코프는 조직 관리자가 정한 범위에 적용된다. project 스코프는 신뢰 게이트 뒤에서만 로드되고, 코드를 실행하는 컴포넌트는 추가로 제한된다. "repo(저장소) 유래 구성은 검증 후 로드"가 설계 원칙이다. enabledPlugins(켜 둔 플러그인 목록을 적는 설정 키)는 한 번 기록되면 업데이트를 넘어 지속된다.
채널(Channels, research preview 단계 — 정식 기능이 되기 전의 시험판)은 세션 안으로 바깥 이벤트를 밀어 넣는 MCP 서버다. 단방향(웹훅/알림을 받기만 함)·양방향(reply tool로 답장까지 함)에 더해 permission relay(권한 중계)로 도구 승인 프롬프트를 폰으로 보내고, yes <id>/no <id>로 원격 승인까지 한다. 발신자 allowlist(허용 목록) 게이팅이 명문 요구사항이다. 바깥에서 들어오는 메시지는 프롬프트 인젝션(injection — 문서나 메시지에 숨긴 지시로 AI를 조종하는 공격)의 통로가 되기 때문이다.
CI 서버·모니터링·챗봇이 로컬 포트로 POST(데이터를 담아 보내는 웹 요청)를 보내거나, 채널 서버가 플랫폼 API를 폴링(주기적으로 새 메시지가 있는지 물어보기)한다message.from.id(보낸 사람의 고유 번호) 기준으로 거른다. room(대화방) 기준은 금지다. 그룹방에서는 누구나 끼어들 수 있어 우회된다손수 구현 중인 여섯 가지에 네이티브(공식 내장) 대체가 문서에 이미 있었다. 첫째, 블랭킷 bypass(승인을 전부 건너뛰는 설정)는 --permission-prompt-tool로 바꿀 수 있다. 둘째, 자체 재귀 가드는 CLAUDE_CODE_CHILD_SESSION=1 검사로 대신한다. 셋째, 2h 외부 kill(두 시간 뒤 바깥에서 프로세스를 죽이는 타이머)은 --max-turns/--max-budget-usd로 프로세스 안에서 건다. 넷째, 주입 과중(스폰 실패)은 --exclude-dynamic-system-prompt-sections로 푼다. 다섯째, nohup/tmux 수제 백그라운드는 --bg+claude agents가 공식판이다. 여섯째, telegram rc 브릿지(텔레그램으로 원격 승인을 중계하던 자체 장치)는 Channels permission relay(preview 단계, allowlist 제약)로 대체된다. 그리고 네 번째 분기가 있다. 대화형과 워커는 "되돌리기·조향"을 각각 세션 내부 장치(체크포인트·Shift+Tab·채널)와 세션 외부 장치(worktree·타임아웃·보드)로 푼다.