Claude Code CLI에는 세션 시작 시 자동으로 실행되는 훅(SessionStart hooks)이 있습니다. 이 메커니즘을 사용하여 세션 시작 시 프록시 구동, 시크릿 동기화, 보안 검사 등을 자동화할 수 있습니다.
본 글에서는 11개의 SessionStart 훅을 운영하며 얻은 지식과 직면했던 문제 해결 기록을 소개합니다.
Claude Code의 설정 파일(~/.claude/settings.json)에 세션 시작 시 실행할 명령어를 등록할 수 있습니다.
{
"hooks": {
"SessionStart": [
...
}
Claude Code가 실행될 때, 등록된 스크립트들이 순차적으로 실행됩니다. 표준 출력(stdout)은 Claude Code의 컨텍스트로 전송되며, /dev/tty 또는 >&2를 통해 터미널에 표시됩니다.
~/.claude/scripts/
├ llm/
│ └ start-glm-proxy.sh # GLM Rate Proxy 구동
...
각 훅의 역할을 설명합니다.
LLM 라우팅의 핵심이 되는 로컬 프록시를 구동합니다.
처리 내용:
- 이미 구동 중인지 확인 (
pgrep + health check)
- 구동 중이라면
settings.json의 ANTHROPIC_BASE_URL을 검증
- 미구동이라면 프록시를 백그라운드에서 구동
- 구동 실패 시 Z.AI 직접 URL로 폴백(fallback)
ensure_settings_url() {
if [ -f "$SETTINGS" ]; then
sed -i "s|"ANTHROPIC_BASE_URL": "[^"]*"|"ANTHROPIC_BASE_URL": "$1"|" "$SETTINGS"
...
}
중요한 점은, 프록시가 healthy 상태일지라도 URL 검증을 실행해야 한다는 것입니다. PC를 이전하는 등의 상황으로 settings.json이 초기화되면, 프록시가 작동하고 있더라도 Claude Code가 직접 Z.AI에 연결해버리는 문제가 있었습니다.
~/.secrets.env에서 환경 변수를 읽어와 settings.json의 env 섹션에 동기화합니다. API 키를 파일에 직접 작성하지 않고, 환경 변수를 통해 주입하는 방식입니다.
이전 세션 종료 시 생성된 handoff.md 파일을 읽어 Claude Code의 컨텍스트로 전송합니다.
if [[ -f "$HANDOFF_FILE" ]]; then
echo "--- Handoff ---"
cat "$HANDOFF_FILE"
...
이를 통해 새 세션의 Claude Code가 이전 작업 내용을 이어받을 수 있습니다.
각 훅의 상태를 취합하여 하나의 배너로 터미널에 표시합니다.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚀 Claude Code 세션 시작
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
...
증상: 새로운 터미널에서 claude를 구동했을 때, 시작 배너가 표시되지 않는 경우가 있었습니다.
원인: SessionStart 훅 내부의 쉘은 **비대화형 쉘(non-interactive shell)**로 실행되기 때문에, /dev/tty나 >&2로의 출력이 터미널에 도달하지 않을 수 있습니다. 터미널 상태나 구동 방식에 따라 /dev/tty 연결 가능 여부가 달라져 '될 때와 안 될 때'가 있었습니다.
해결: 배너 출력 기능을 .bashrc의 claude() 쉘 함수(대화형 쉘) 쪽으로 이동했습니다. SessionStart 훅은 상태 파일(/tmp/claude-startup/*.status)에 결과만 기록하고, .bashrc 측에서 이 파일을 읽어와 표시합니다.
훅: 상태 파일 쓰기 (확실함)
↓
.bashrc claude(): 상태 파일 읽기 → 배너 표시 (확실함)
증상: PC 이전 후, GLM→MiniMax 폴백(fallback)이 작동하지 않았습니다.
원인: 프록시가 이미 구동 중일 경우, health check OK로 즉시 exit 0 처리되어, settings.json의 ANTHROPIC_BASE_URL
이전에는 URL 검증을 수행하고 있지 않았습니다. PC를 이전하면서 설정이 초기화되어 프록시를 우회하는 URL로 되어 있었습니다.
해결: 모든 3가지 경로(healthy / 시작 성공 / 시작 실패)에서 ensure_settings_url() 함수를 호출하도록 수정했습니다.
# healthy에서도 URL 검증을 실행
if pgrep -f "python3 -m glm_rate_proxy" > /dev/null 2>&1; then
if curl -sf -m 2 http://127.0.0.1:8787/proxy/status > /dev/null 2>&1; then
...
증상: MCP 서버를 삭제해도 다음 세션 시작 시 복원됩니다.
원인: sync-secrets-to-settings.sh 스크립트가 환경 변수를 settings.json에 동기화할 때, 존재하지 않는 MCP 서버의 항목을 null 값으로 재(再)생성하고 있었습니다.
해결: 스크립트에서 불필요한 MCP 서버의 jq 항목을 삭제했습니다.
훅(Hook)들 사이에 의존 관계를 만들지 않는 것이 중요합니다. 하나의 훅이 실패하더라도 다른 훅들이 작동하도록 해야 합니다.
훅의 실행 결과는 /tmp/claude-startup/ 디렉터리 아래의 상태 파일에 기록됩니다. 이는 다른 훅이나 .bashrc에서 참조하기 쉽게 만듭니다.
stdout (기본값): Claude Code의 컨텍스트로 전송되는 정보. Claude가 읽는 내용입니다.
/dev/tty: 터미널에 직접 표시되며, 사용자가 볼 수 있는 정보입니다.
(stderr) &2: Claude Code 프로세스에 흡수되지만, /dev/tty를 사용할 수 없을 때의 폴백(fallback) 역할을 합니다.
프록시 시작 실패 시 Z.AI 직접 URL로 폴백하는 등, 훅이 실패하더라도 Claude Code 자체는 사용 가능한 상태를 유지합니다.
새로운 PC 환경에서 Claude Code를 설정할 때 최소한 확인해야 할 항목들입니다.
-
~/.secrets.env에 API 키가 설정되어 있는지
-
settings.json에 ANTHROPIC_BASE_URL 항목이 존재하는지
-
start-glm-proxy.sh를 한 번 수동으로 실행하여 동작을 확인했는지
-
grep "ANTHROPIC_BASE_URL" ~/.claude/settings.json 명령어를 통해 프록시 URL인지 확인
-
curl -s http://127.0.0.1:8787/proxy/status를 실행하여 정상 응답을 받았는지
-
WSL2 한정: /dev/tty의 동작이 환경에 의존적이므로, 다른 환경에서는 조정이 필요합니다.
-
훅 개수 증가: 11개의 훅이 순차적으로 실행되므로, 시작하는 데 몇 초가 걸릴 수 있습니다.
-
설정 파일 두 곳 관리: ~/.claude.json과 ~/.claude/settings.json 두 곳 모두를 인지해야 합니다.
SessionStart 훅은 Claude Code CLI를 자신의 개발 워크플로우에 맞게 조정하는 강력한 메커니즘입니다. 프록시 시작, 시크릿 관리, 세션 이관 등을 자동화함으로써 세션 시작 시의 수동 작업을 제로화할 수 있습니다.
다만, 비대화형 셸(non-interactive shell)에서의 실행이라는 제약이 있기 때문에, 사용자에게 보여주는 정보는 .bashrc 측에서 출력하는 등의 노력이 필요합니다. 훅 설계에서는 '독립성', '페일세이프(fail-safe)', 'stdout/tty 사용 분리'의 세 가지 포인트를 염두에 두면 안정적인 구성을 할 수 있습니다.
- Claude Code의 MCP 도구가 컨텍스트를 압박하는 문제 조사 및 최적화 기록
- Claude Code CLI를 GLM (Z.AI)으로 대체한 이야기 (비용 대폭 절감 실측)
- 월간 27억 토큰을 처리한 LLM 라우팅의 실제 운영 리포트
본 기사는 개인 Zenn에서도 공개하고 있습니다.