Files
2026-04-07 20:35:45 +09:00

8.4 KiB

name, description
name description
aptabase Aptabase 커밋 기준 토큰 기록 훅을 설치/검증/디버깅한다. 사용자가 "aptabase 설정", "aptabase 테스트", "토큰 기록 확인", "누적 토큰"을 요청할 때 사용.

Aptabase 커밋 기준 토큰 기록

Claude 의 모든 응답 토큰을 프로젝트 단위로 누적하고, git 커밋이 발생하면 Aptabase 로 한 번에 flush 한 뒤 0 으로 리셋한다.

구조

.claude/hooks/
├── aptabase.json              # 설정 (app_key, aptabase_host, user_name)
├── aptabase_common.py         # 공통 유틸 (Python)
├── aptabase-accumulate.sh     # Stop 훅 진입점
├── aptabase-accumulate.py     # Stop 로직 — 토큰 누적만
├── aptabase-commit.sh         # 커밋 flush 진입점 (Claude 훅 + git 훅 공용)
├── aptabase-commit.py         # 커밋 감지 + Aptabase POST + 리셋
└── install-git-hook.sh        # .git/hooks/post-commit 설치 스크립트

.claude/state/
└── aptabase-accum.json        # 누적 상태 (자동 생성, git root 기준)

.git/hooks/
└── post-commit                # install-git-hook.sh 가 설치 (모든 커밋 포착)

동작 방식

시점 하는 일
응답 완료 Stopaptabase-accumulate.sh 트랜스크립트 새 영역 파싱 → state.accum 에 토큰 더하기 (네트워크 없음)
Claude 의 Bash 툴 실행 후 PostToolUse(Bash)aptabase-commit.sh HEAD 변화 감지. 바뀌었으면 flush → POST → 리셋
모든 커밋 (IDE / 터미널 / GUI) .git/hooks/post-commitaptabase-commit.sh 같은 로직. Claude 밖에서 이루어진 커밋도 포착

aptabase-commit.py 는 stdin 에 JSON 이 있으면 Claude 훅 모드, 비어 있으면 git 훅 모드로 동작한다. 두 경로가 중복 실행돼도 last_sent_commit 비교 덕에 이중 전송되지 않는다.

  • 누적 범위: <git-root>/.claude/state/aptabase-accum.json — 프로젝트(= git 저장소)마다 독립
  • 커밋 감지: HEAD 해시 변화 기반 — git commit, --amend, merge, cherry-pick, rebase 모두 감지
  • 첫 실행: 기존 HEAD 를 last_sent_commit 에 기준점으로 기록만 하고 전송 안 함 (이전 작업 토큰이 0이라)
  • 전송 실패: state 유지 → 다음 Bash 호출에서 재시도 (누적값 + 새 커밋 병합)

1. 설정

.claude/hooks/aptabase.json:

{
  "enabled": true,
  "app_key": "A-SH-XXXXXXXXXX",
  "aptabase_host": "https://aptabase.example.com",
  "user_name": "kim"
}
의미
enabled false 로 두면 훅이 즉시 종료 (일시 비활성)
app_key Aptabase App Key (self-hosted 는 A-SH- 접두사)
aptabase_host Aptabase 인스턴스 base URL
user_name props.user_name 으로 전송할 사용자 식별자

2. settings.json 에 훅 등록

.claude/settings.jsonhooks 에 두 항목 모두 등록:

{
  "hooks": {
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/aptabase-accumulate.sh" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/aptabase-commit.sh" }
        ]
      }
    ]
  }
}

이미 다른 Stop / PostToolUse 훅이 있으면 같은 hooks 배열에 추가한다.

2-1. git post-commit 훅 설치 (필수)

Claude 외부에서 이루어진 커밋(IDE, 터미널, git GUI 등)도 포착하려면 .git/hooks/post-commit 을 설치해야 한다. 프로젝트 루트에서 한 번만 실행:

bash .claude/hooks/install-git-hook.sh

이미 .git/hooks/post-commit 이 존재하면 에러가 나면서 병합 방법을 안내한다. 기존 훅을 덮어쓰려면:

bash .claude/hooks/install-git-hook.sh --force     # 기존 훅을 .bak 로 백업하고 덮어쓰기

제거:

bash .claude/hooks/install-git-hook.sh --uninstall

.git/hooks/ 는 버전 관리되지 않으므로 clone 할 때마다 재설치 필요하다. CI/자동 설정 스크립트에서 프로젝트 초기화 시 함께 실행하는 것을 권장한다.

3. 전송되는 payload

Aptabase 이벤트 이름은 claude_commit (고정).

systemProps

  • osName, osVersion, locale, appVersion, sdkVersion, isDebug

props

필드 출처
claude_oauth_id ~/.claude/config.jsonoauthAccount.emailAddress (없으면 anonymous)
plan oauthAccount.subscriptionType (max/pro/team/enterprise) — API 키면 apikey
user_name aptabase.jsonuser_name
local_ip UDP connect 트릭으로 추출 (패킷 미전송)
public_ip api.ipify.org / ifconfig.me (2초 타임아웃)
commit_hash git rev-parse HEAD
commit_message git log -1 --pretty=%B
issue_number 커밋 메시지에서 regex 추출 (FEAT-123, #123, fixes #45 등). 없으면 null
repository owner/repo (remote URL) 또는 디렉터리명
repository_url git config --get remote.origin.url
total_tokens 누적 합계
input_tokens, cache_creation_tokens, cache_read_tokens, output_tokens 누적 세부

Aptabase 최상위 sessionId 는 epoch+random 으로 매 이벤트 새로 생성된다. Claude 세션 UUID 는 사용하지 않는다.

4. 연결 테스트

단계 1 — Aptabase 도달 가능성 확인 (curl):

APP_KEY=$(jq -r .app_key .claude/hooks/aptabase.json)
HOST=$(jq -r .aptabase_host .claude/hooks/aptabase.json)
curl -i -X POST "$HOST/api/v0/event" \
  -H "Content-Type: application/json" \
  -H "App-Key: $APP_KEY" \
  -H "User-Agent: ClaudeCodeHook/test" \
  -d '{
    "timestamp":"2026-04-07T00:00:00.000Z",
    "sessionId":"manual-test",
    "eventName":"claude_commit",
    "systemProps":{"osName":"Test","sdkVersion":"manual"},
    "props":{"commit_message":"manual test","total_tokens":1}
  }'

200 + {} 이면 Aptabase 수신 OK.

단계 2 — 훅 직접 실행 (Stop 누적):

echo '{"transcript_path":"","cwd":"'"$PWD"'","hook_event_name":"Stop"}' \
  | bash .claude/hooks/aptabase-accumulate.sh
cat .claude/state/aptabase-accum.json

transcript_path 가 비어있으면 accum 은 0 그대로지만, state 파일은 생성돼야 한다.

단계 3 — 실제 동작 확인:

  1. Claude 와 대화해서 토큰 쌓기
  2. cat .claude/state/aptabase-accum.json 으로 누적 확인
  3. git commit 실행
  4. Aptabase 대시보드에서 claude_commit 이벤트 확인
  5. cat .claude/state/aptabase-accum.jsonaccum 이 0 으로 리셋, last_sent_commit 갱신됐는지 확인

5. 누적 상태 파일 포맷

<git-root>/.claude/state/aptabase-accum.json:

{
  "accum": {
    "input_tokens": 1234,
    "cache_creation_tokens": 5678,
    "cache_read_tokens": 9012,
    "output_tokens": 345
  },
  "offsets": {
    "C:\\Users\\...\\projects\\d--foo\\session-uuid.jsonl": 45678
  },
  "last_sent_commit": "abc123def..."
}
  • offsets: 트랜스크립트 JSONL 파일별로 이미 읽은 byte 위치. append-only 특성 덕에 중복 집계 방지
  • 수동 리셋이 필요하면 rm .claude/state/aptabase-accum.json

6. 트러블슈팅

누적이 안 쌓임 (accum 이 계속 0):

  1. aptabase.jsonenabled: true
  2. app_key, aptabase_host 실제 값
  3. settings.json 의 Stop 훅에 aptabase-accumulate.sh 등록
  4. transcript_path 가 훅 입력 JSON 에 실제로 있는지
  5. 해당 파일 읽기 권한

커밋했는데 Aptabase 에 안 뜸:

  1. settings.json 의 PostToolUse(Bash) 훅에 aptabase-commit.sh 등록
  2. last_sent_commit 가 이미 현재 HEAD 와 같은지 (이미 전송된 상태)
  3. curl 로 직접 POST 했을 때 200 이 오는지 (네트워크/인증 분리 검증)
  4. python3 --version 동작
  5. git 저장소 안에서 동작 중인지 (git rev-parse HEAD 성공해야 함)

첫 커밋이 무시됨: 의도된 동작. last_sent_commit 가 빈 상태일 때는 현재 HEAD 를 기준점으로 기록만 하고 전송하지 않는다. 다음 커밋부터 전송된다.

전송 기준점 초기화:

jq '.last_sent_commit = ""' .claude/state/aptabase-accum.json > /tmp/_s && mv /tmp/_s .claude/state/aptabase-accum.json

일시 비활성: aptabase.json 에서 enabled: false. 훅 등록은 유지해도 된다.