Skip to content

Instantly share code, notes, and snippets.

@spilist
Last active April 17, 2026 00:02
Show Gist options
  • Select an option

  • Save spilist/b0db92a859192f5ec6199d3f35a81b98 to your computer and use it in GitHub Desktop.

Select an option

Save spilist/b0db92a859192f5ec6199d3f35a81b98 to your computer and use it in GitHub Desktop.
Claude Code includeGitInstructions cache miss reproduction (issue #47107)

Claude Code includeGitInstructions 캐시 미스 실측

anthropics/claude-code#47107 재현 실험. Claude Code의 시스템 프롬프트 마지막 블록에 git status / Recent commits가 들어가서, git 상태가 바뀔 때마다 그 블록이 통째로 prompt cache miss를 내는 현상을 측정했습니다.

환경

  • Claude Code 2.1.112
  • 모델: claude-haiku-4-5 (캐시 동작 자체는 모델 무관)
  • 측정 지표: claude -p ... --output-format jsonusage.cache_read_input_tokens / usage.cache_creation_input_tokens
  • 격리된 임시 git 저장소에서 동일 프롬프트(say only the word: ping) 반복

실험 1 — CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS 끄기 전후 비교

cache_experiment.sh 결과:

# 조건 cache_read cache_create
1 fresh 0 32,381
2 repeat 29,687 2,694
3 empty commit 후 23,663 8,725
4 repeat 29,694 2,694
5 disabled fresh 0 30,691
6 disabled repeat 27,997 2,694
7 disabled + empty commit 27,997 2,694
8 disabled repeat 27,997 2,694
  • 기본 동작에서는 git commit --allow-empty 한 번만으로 약 6,000 토큰 (= 8,725 − 2,694) cache write 추가 발생.
  • CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1이면 같은 commit 후에도 baseline(2,694) 그대로 유지.

실험 2 — 어떤 git 변동이 캐시를 깨는가

cache_pinpoint.sh 결과:

# 변동 결과 원인
1 단순 반복 HIT -
2 tracked 파일 touch (mtime만) HIT status 텍스트 동일
3 untracked 생성 후 즉시 삭제 HIT status 다시 clean
4 untracked 파일 존재 MISS Untracked files: 추가
5 branch round-trip (foo→main) HIT 최종 branch명 동일
6 다른 branch에 머무름 (bar) MISS Current branch: bar
7 empty commit MISS Recent commits 변동

캐시를 깨는 실제 트리거:

  1. Recent commits 목록 변화 — 새 커밋, amend, rebase, reset. 가장 자주 발생.
  2. Branch 이름 변화 — checkout으로 다른 브랜치로 이동.
  3. git status 출력 변화 — untracked 파일 추가, working tree dirty.

mtime 변화나 net no-op 작업(touch, create+delete)은 영향 없음.

정리

이슈 제목은 git status로 단순화돼 있지만, 실제로는 시스템 프롬프트의 git 컨텍스트 블록 전체(branch + status + recent commits)가 한 덩어리로 캐시되며, 그중 Recent commits가 일상 워크플로에서 가장 자주 변동하는 부분입니다. 즉 "커밋 한 번 = 캐시 미스 한 번"이 사용자 입장에서 가장 체감되는 비용.

임시 우회: CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1 또는 settings.json"includeGitInstructions": false.

근본 수정 방향(이슈 제안):

  • 가변 git 컨텍스트를 user message로 옮기거나
  • 별도 system[].text 블록으로 분리해 cache_control 경계를 정리

재현 방법

chmod +x cache_experiment.sh cache_pinpoint.sh
./cache_experiment.sh
./cache_pinpoint.sh

각 스크립트는 mktemp -d로 독립 git repo를 만들어 현재 작업 디렉터리에 영향을 주지 않습니다. 총 호출 수는 두 스크립트 합 ~20회로, haiku 기준 약 $0.10 미만.

#!/usr/bin/env bash
set -euo pipefail
WORKDIR=$(mktemp -d)
cd "$WORKDIR"
git init -q
git config user.email "test@test.local"
git config user.name "test"
echo "hello" > file.txt
git add file.txt
git commit -q -m "initial"
PROMPT='say only the word: ping'
run() {
local label="$1"
local extra_env="${2:-}"
local out read_tok create_tok
out=$(env $extra_env claude -p "$PROMPT" --model claude-haiku-4-5 --output-format json 2>&1)
read_tok=$(echo "$out" | jq -r '.usage.cache_read_input_tokens')
create_tok=$(echo "$out" | jq -r '.usage.cache_creation_input_tokens')
printf "%-45s read=%6s create=%6s\n" "$label" "$read_tok" "$create_tok"
}
echo "workdir=$WORKDIR"
echo
echo "=== Default (git instructions enabled) ==="
run "1. fresh"
run "2. repeat"
git commit --allow-empty -q -m "dummy1"
run "3. after empty commit"
run "4. repeat (same dirty=clean state)"
echo
echo "=== CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1 ==="
run "5. fresh disabled" "CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1"
run "6. repeat disabled" "CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1"
git commit --allow-empty -q -m "dummy2"
run "7. after empty commit disabled" "CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1"
run "8. repeat disabled" "CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1"
echo
echo "=== Bonus: dirty working tree ==="
echo "modified" >> file.txt
run "9. dirty state (uncommitted change)"
run "10. dirty repeat"
echo "modified again" >> file.txt
run "11. different dirty state"
#!/usr/bin/env bash
set -euo pipefail
WORKDIR=$(mktemp -d)
cd "$WORKDIR"
git init -q -b main
git config user.email "test@test.local"
git config user.name "test"
echo "hello" > file.txt
git add file.txt
git commit -q -m "initial"
PROMPT='say only the word: ping'
run() {
local label="$1"
local out read_tok create_tok
out=$(claude -p "$PROMPT" --model claude-haiku-4-5 --output-format json 2>&1)
read_tok=$(echo "$out" | jq -r '.usage.cache_read_input_tokens')
create_tok=$(echo "$out" | jq -r '.usage.cache_creation_input_tokens')
printf "%-50s read=%6s create=%6s\n" "$label" "$read_tok" "$create_tok"
}
run "0. warm cache"
run "1. baseline repeat (control: should hit)"
# touch + rm tracked file (status stays clean)
touch file.txt && sleep 1
run "2. after touch tracked file (status clean)"
# create + delete untracked
echo x > tmp.txt && rm tmp.txt
run "3. after create+delete untracked (status clean)"
# new untracked file (status changes)
echo x > untracked.txt
run "4. with untracked file present (status changed)"
rm untracked.txt
# branch switch and back
git checkout -q -b foo
git checkout -q main
run "5. after branch round-trip (back on main)"
# stay on a different branch
git checkout -q -b bar
run "6. on different branch 'bar'"
git checkout -q main
# control: empty commit
git commit --allow-empty -q -m "dummy"
run "7. control: after empty commit"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment