Hooks 환경 변수: CLAUDE_PROJECT_DIR과 CLAUDE_ENV_FILE

Claude Code는 훅 프로세스에 프로젝트 정보를 환경 변수로 넘겨줍니다. CLAUDE_PROJECT_DIR로 프로젝트 루트를 얻고, CLAUDE_ENV_FILE로 세션 중 환경 변수를 영속화하는 방법과 그 밖의 변수 목록을 정리합니다.

12분 갱신

지금까지의 훅 예제는 전부 "$CLAUDE_PROJECT_DIR"/.claude/hooks/... 경로를 썼습니다. 이 변수는 어디서 오고, 그 밖에 무엇이 훅에 넘어올까요. 이 글은 훅이 물려받는 환경을 정리하고 CLAUDE_PROJECT_DIR과 CLAUDE_ENV_FILE을 실습으로 확인합니다. 우리 팀이 두 대의 맥에서 같은 설정 파일을 쓸 수 있는 것도 절대 경로 대신 이 변수로 적었기 때문입니다. 다음 글은 매처입니다.

핵심 요약

  • 훅은 Claude Code의 환경을 그대로 물려받고, 거기에 CLAUDE_PROJECT_DIR이 항상 추가됩니다.
  • CLAUDE_ENV_FILE은 SessionStart, Setup, CwdChanged, FileChanged 네 이벤트에서만 주어지는 파일 경로입니다. 여기 쓴 export 줄이 세션 내내 Bash 도구에 적용됩니다.
  • 플러그인 훅에는 CLAUDE_PLUGIN_ROOT·CLAUDE_PLUGIN_DATA, 웹 환경에는 CLAUDE_CODE_REMOTE, 도구 컨텍스트에는 CLAUDE_EFFORT가 추가됩니다.
  • $CLAUDE_MODEL 같은 변수는 없습니다. 모델은 SessionStart 입력 JSON의 model 필드로만 알 수 있습니다.

실습용 디렉터리

앞 글의 ~/hook-lab을 씁니다.

mkdir -p ~/hook-lab/.claude/hooks && cd ~/hook-lab && git init -q

여기서 claude를 띄우면 이 디렉터리가 프로젝트 루트입니다(Windows Git Bash도 동일).

Hooks에서 쓸 수 있는 환경 변수는 무엇인가

훅은 Claude Code가 실행된 셸 환경을 상속합니다(OTEL_* 텔레메트리 변수만 제거됩니다). 그 위에 추가되는 변수는 다음과 같습니다.

변수 값 어디서
CLAUDE_PROJECT_DIR 프로젝트 루트 절대 경로 모든 훅. stdio MCP 서버와 플러그인 LSP 서버 환경에도 설정
CLAUDE_ENV_FILE export 줄을 추가할 파일 경로 SessionStart, Setup, CwdChanged, FileChanged만
CLAUDE_PLUGIN_ROOT 플러그인 설치 디렉터리(업데이트마다 바뀜) 플러그인 훅
CLAUDE_PLUGIN_DATA 플러그인 영속 데이터 디렉터리(업데이트 후에도 유지) 플러그인 훅
CLAUDE_CODE_REMOTE 웹 환경이면 "true", 로컬 CLI에서는 미설정 모든 훅
CLAUDE_CODE_BRIDGE_SESSION_ID Remote Control 세션 ID(v2.1.199 이상) 원격 제어 연결 중
CLAUDE_EFFORT low·medium·high·xhigh·max PreToolUse, PostToolUse, Stop, SubagentStop 같은 도구 컨텍스트

없는 것도 알아 두세요. $CLAUDE_MODEL은 없습니다. 셸의 ANTHROPIC_MODEL은 상속되지만 /model로 바꾼 값은 반영되지 않고, 현재 모델은 SessionStart 입력 JSON의 model 필드로만 알 수 있습니다. 세션 ID와 작업 디렉터리도 stdin JSON의 session_id, cwd로 옵니다.

먼저 간단한 예로 확인하기

어떤 변수가 실제로 오는지 파일로 떨궈 봅니다.

cat > .claude/hooks/dump-env.sh <<'EOF'
#!/bin/bash
INPUT=$(cat)
{
  echo "=== $(date '+%T') $(echo "$INPUT" | jq -r '.hook_event_name')"
  env | grep '^CLAUDE_' | sort
  echo "cwd(json)=$(echo "$INPUT" | jq -r '.cwd')"
} >> "$HOME/hook-env.log"
exit 0
EOF
chmod +x .claude/hooks/dump-env.sh
{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/dump-env.sh" } ] }
    ],
    "PostToolUse": [
      { "matcher": "Bash", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/dump-env.sh" } ] }
    ]
  }
}

새 세션에서 "pwd 실행해 줘"라고 한 뒤 cat ~/hook-env.log를 보면 SessionStart 블록에는 CLAUDE_ENV_FILE이 있고 PostToolUse 블록에는 없습니다. 이 차이를 직접 보는 것이 목적입니다.

CLAUDE_PROJECT_DIR: 프로젝트 루트는 어떻게 얻나

CLAUDE_PROJECT_DIR은 Claude Code를 실행한 프로젝트의 루트입니다. 입력 JSON의 cwd는 "훅이 호출된 시점의 작업 디렉터리"라 Claude가 cd를 한 뒤에는 달라질 수 있습니다. 파일을 찾을 때는 CLAUDE_PROJECT_DIR, 실행 위치를 볼 때는 cwd입니다.

설정 파일에서 쓸 때는 두 가지 형식이 있습니다.

{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check.sh" }
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check.sh", "args": [] }

첫 번째는 셸 형식이라 공백 경로를 위해 큰따옴표로 감쌉니다. 두 번째는 args가 있어 exec 형식으로 실행되며 ${CLAUDE_PROJECT_DIR}이 문자열로 치환되므로 따옴표가 필요 없습니다. 경로 자리 표시자를 쓰는 훅은 exec 형식이 공식 권장입니다. 스크립트 안에서는 cd "$CLAUDE_PROJECT_DIR"로 시작해 상대 경로 사고를 없앱니다.

CLAUDE_ENV_FILE: 세션 중 환경 변수를 어떻게 영속화하나

훅은 매번 새 프로세스라 export가 남지 않습니다. SessionStart 등 네 이벤트에서 이 변수에 파일 경로가 담겨 오고, 그 파일에 적은 export 줄은 이후 Claude가 실행하는 모든 Bash 명령 앞에 프리앰블로 실행됩니다.

#!/bin/bash
# .claude/hooks/session-env.sh
[ -z "$CLAUDE_ENV_FILE" ] && exit 0
{
  echo "export PROJECT_ROOT=\"$CLAUDE_PROJECT_DIR\""
  echo 'export NODE_OPTIONS="--max-old-space-size=4096"'
  echo 'export PATH="$PATH:./node_modules/.bin"'
} >> "$CLAUDE_ENV_FILE"
exit 0

>>로 추가하는 이유는 다른 훅이 먼저 써 둔 줄을 지우지 않기 위해서입니다. 새 세션에서 "echo $PROJECT_ROOT 실행해 줘"라고 시켜 값이 찍히면 영속화된 것입니다. nvm, mise처럼 변수를 한꺼번에 바꾸는 도구는 실행 전후 export -p를 comm -13으로 비교해 달라진 줄만 붙이는 패턴을 공식 문서가 제시하고, direnv는 direnv export bash > "$CLAUDE_ENV_FILE" 한 줄이면 됩니다. 같은 줄을 CwdChanged에 걸면 디렉터리를 옮길 때마다 다시 로드됩니다.

쓰임새는 도구 경로, 테스트 DB 주소, 로그 레벨처럼 "이 세션의 모든 명령에 공통인" 값입니다. API 키 원문은 적지 마세요. 평문으로 남고 stdout에 찍히면 컨텍스트에까지 들어갑니다. 키는 셸 환경이나 시크릿 관리자에서 상속받게 둡니다.

정리

  • 훅은 부모 환경을 상속하고 CLAUDE_PROJECT_DIR이 항상 추가됩니다.
  • cwd는 호출 시점 디렉터리, CLAUDE_PROJECT_DIR은 프로젝트 루트. 파일 참조는 후자로 합니다.
  • CLAUDE_ENV_FILE은 네 이벤트에서만 오고, >>로 추가한 export 줄이 세션 내내 적용됩니다.
  • 모델명 환경 변수는 없습니다. SessionStart 입력의 model을 씁니다.

자주 묻는 질문

CLAUDE_PROJECT_DIR은 어떤 값이 들어오나요?

Claude Code를 실행한 프로젝트의 루트 절대 경로입니다. 모든 훅과 stdio MCP 서버에 설정됩니다. Claude가 cd로 이동해도 바뀌지 않으므로 이동 후 위치는 입력 JSON의 cwd를 보세요.

CLAUDE_ENV_FILE은 모든 훅에서 쓸 수 있나요?

아닙니다. SessionStart, Setup, CwdChanged, FileChanged 네 이벤트에서만 옵니다. 다른 훅에서는 비어 있으므로 [ -n "$CLAUDE_ENV_FILE" ]로 검사한 뒤 씁니다.

환경 변수가 실제로 넘어오는지 어떻게 확인하나요?

훅 스크립트에서 env | grep '^CLAUDE_' >> ~/hook-env.log처럼 파일로 떨군 뒤 열어 봅니다. 이벤트별 차이를 보려면 hook_event_name도 함께 기록합니다. claude --debug-file의 로그로 확인해도 됩니다.

환경 변수를 영속화하면 어디에 쓸 수 있나요?

그 세션의 모든 Bash 명령에서 그 변수가 보입니다. 도구 경로(PATH), Node 메모리 옵션, 테스트 DB 주소, 로그 레벨, direnv·nvm 환경처럼 "매 명령 앞에 붙이던" 설정을 한 번에 끝냅니다.

팀에 클로드코드를 도입하려면 실제 업무에 맞춘 설계가 필요합니다. 무료 상담 신청 →