Claude Code 세션 여러 개, 누가 나를 기다리는지 한눈에 보기
tmux 창마다 Claude Code를 하나씩 띄워 놓고 쓰다 보니, 어느 세션이 승인을 기다리는지 모르고 방치하는 일이 잦았다. 별도 계측 없이 Claude Code가 이미 남기는 파일만으로 세션 상태·마지막 요청·사용 한도를 한 화면에 모은 기록. 결과물은 로컬 웹 대시보드 하나와 메뉴바 아이템 하나다.
문제: 탭을 돌면서 “다 됐나?” 확인하기
Claude Code를 한 세션만 쓸 때는 아무 문제가 없다. 세 개, 네 개를 동시에 돌리기 시작하면 다른 종류의 일이 생긴다.
- A 세션이 도구 실행 승인을 기다리는데 나는 B 세션을 보고 있다. 5분 뒤에야 발견한다.
- C 세션은 진작 끝났는데 안 봐서 다음 지시가 늦어진다.
- 어느 창이 어느 프로젝트였는지, 마지막에 뭘 시켰는지 헷갈려서 창을 하나씩 열어 본다.
문제의 본질은 세션의 상태가 세션 안에만 있다는 것이다. 창을 열어야 보인다. 그래서 창 밖으로 꺼내기로 했다.
발견: 계측이 필요 없다
처음엔 훅으로 상태를 기록하는 계측 레이어를 만들 생각이었다. 그런데 ~/.claude/sessions/ 를 열어보니 이미 다 있었다.
// ~/.claude/sessions/<pid>.json — Claude Code 가 실시간으로 갱신한다
{
"sessionId": "0b3e18a4-…",
"cwd": "/Users/me/workspace",
"tmux": "projects:@2.%10",
"status": "busy",
"name": "workspace-7b",
"statusUpdatedAt": 1788999269241
}
세션마다 파일 하나. status 가 idle / busy / waiting 으로 바뀌고 tmux 필드에 세션·윈도우·pane 주소까지 들어 있다. pid 가 살아 있는지로 on/off 를 판정하면 끝이다.
마지막 요청은 트랜스크립트에서 뽑는다. ~/.claude/projects/<cwd를 -로 치환>/<sessionId>.jsonl 의 꼬리에서 마지막 user 메시지를 찾으면 된다. 도구 출력이 길면 꼬리 400KB 안에 없을 수 있어서 400KB → 4MB → 전체 순으로 범위를 넓힌다.
그래서 스캐너는 파이썬 한 파일이다. 이 파일을 웹과 메뉴바가 같이 쓴다.
~/.claude/sessions/*.json ─┐
~/.claude/projects/*/*.jsonl ┼─▶ cc_sessions.py (scan) ─┬─▶ cc-web.py → 127.0.0.1:7777
tmux list-panes ────────────┘ └─▶ SwiftBar 플러그인 (2초)
▲
훅(Stop/Notification) → ~/.claude/cc-state/<sid> ─────────────┘ done / waiting 보조 신호
상태 판정: “확인했다”의 기준
status 만으로는 부족한 경우가 둘 있다.
완료(done)와 유휴(idle)의 구분. Claude Code 입장에선 둘 다 idle 이다. 하지만 나한테는 “끝났는데 아직 안 봤다”와 “봤고 다음 지시를 생각 중이다”가 다르다. 그래서 Stop 훅에서 그 pane 이 지금 포커스돼 있는지(#{window_active}#{pane_active} 둘 다 1)를 본다. 보고 있었으면 idle, 아니면 done. 나중에 그 pane 으로 이동하면 tmux 의 pane-focus-in 훅이 done 을 idle 로 되돌린다. “확인했다”의 기준을 pane 포커스로 정한 것이다.
승인 대기(waiting). 최신 버전은 status 에 waiting 을 직접 써 주지만, 그 전 버전을 위해 Notification 훅도 받는다. 여기 함정이 하나 있다. Notification 훅은 승인 요청과 60초 무응답 알림 두 경우에 다 발화한다. 구분 없이 waiting 으로 쓰면 완료 표시가 1분 만에 덮여 사라진다. payload 의 message 에 permission 이 들어 있을 때만 waiting 으로 보고, 무응답 알림은 기존 상태를 건드리지 않는다.
tmux 상태바에도 점을 찍는데, 처음에 tmux set -w 로 윈도우 옵션에 쓰다가 한 윈도우에 Claude pane 이 둘이면 서로 덮어쓰는 걸 봤다. pane 옵션(set -p)으로 바꾸고, 윈도우 대표 상태는 pane 들을 훑어 대기 > 완료 > 처리중 > 유휴 우선순위로 집계한다. 집계할 때 #{@cc} 포맷은 윈도우 값을 상속해서 못 쓰고 tmux show -p -v @cc 를 써야 pane 값만 나온다.
네 벌 만들고 두 벌을 버렸다
같은 스캐너 위에 화면을 네 가지로 만들어 봤다.
- tmux 팝업 (
prefix + Tab, curses) — 제일 먼저 만들었고 제일 먼저 버렸다. 팝업을 열려면 결국 터미널에 있어야 한다. - 로컬 웹 (
:7777) — 카드 목록, 마지막 응답 펼치기, 다음 프롬프트 이어서 보내기, 상태 전이 알림. 같은 와이파이의 폰에서도 열린다. PWA 로 Dock 에 박아 뒀다. - 바탕화면 위젯 (Übersicht) — 만들고 몇 시간 뒤에 버렸다. 이유는 아래.
- 메뉴바 (SwiftBar) — 지금 제일 자주 보는 것.
위젯 얘기를 조금 하면, 처음엔 macOS 정식 위젯(WidgetKit)을 생각했다. 안 된다. 타임라인 갱신을 시스템이 통제해서 사실상 분 단위고, 위젯 확장은 샌드박스라 ~/.claude/sessions 를 앱 그룹 우회 없이는 못 읽는다. “지금 승인 대기 중” 을 보는 용도엔 맞지 않는다. Übersicht 는 HTML 위젯이라 웹 화면을 거의 그대로 얹을 수 있었는데, 클릭이 먹으려면 enableInteraction 을 켜야 하고 그러면 바탕화면 클릭을 가로챈다. 메뉴바가 생기자 존재 이유가 없어졌다.
결론은 웹 + 메뉴바. 웹은 조작이 필요할 때, 메뉴바는 힐끔 볼 때.
메뉴바: 점 세 개와 숫자 하나
SwiftBar 는 스크립트의 표준 출력이 곧 메뉴다. 파일명의 2s 가 갱신 주기다.

●◉○ 82% ← 메뉴바에 보이는 글자
---
0.1 onsha-4f busy 2m
피드백을 받을 수 있는 창구가 있으면 좋겠네
1.1 credit-system-kotlin-5f done 34s
Git 차이 확인
2.0 workspace-7b idle 7m
메뉴바의 이 부분은 최소화 해줘
---
한도
5시간 ▓▓▓▓▓▓▓▓░░ 82% 13:10
주간 ▓░░░░░░░░░ 8% 9/17 07:00
Fable ▓░░░░░░░░░ 13% 9/17 07:00
---
토큰
오늘 72.1M out 312k
7일 1.4B out 5.4M

세션마다 점 하나 — ◆ 승인 대기, ● 처리중, ◉ 완료, ○ 유휴. 종료된 세션은 안 그린다. 점 뒤 숫자는 5시간 창 사용률이다. 줄을 누르면 그 pane 으로 이동하고 터미널이 앞으로 나온다.
작은 함정 둘. 한글은 Menlo 에서 두 칸이라 f"{s:<6}" 로 맞추면 열이 어긋난다 — 표시 폭으로 채워야 한다. 그리고 색은 color=밝은바,어두운바 쌍으로 준다. 밝은 메뉴는 반투명 회색 바탕이라 중간 회색이 묻혀서, 유휴 줄은 생각보다 훨씬 진한 회색까지 내려야 읽힌다.
둘째 줄에는 pane 제목이 아니라 마지막 프롬프트를 쓴다. Claude Code 가 pane 에 붙이는 제목은 세션 주제로 한 번 정해지고 잘 안 바뀌어서, 지금 뭘 시켰는지 보려면 프롬프트가 맞다. 처음엔 제목을 썼다가 “예전 걸 그대로 물고 있다”는 걸 며칠 뒤에 알았다.
한도와 토큰
세션 상태만 보다가 욕심이 났다. 5시간 한도가 얼마나 남았는지, 오늘 토큰을 얼마나 썼는지도 같은 자리에 있으면 좋겠다.
한도는 Claude Code 의 /usage 명령이 치는 엔드포인트를 그대로 부른다. 로그인 토큰은 키체인의 Claude Code-credentials 항목에 JSON 으로 들어 있다.
r = subprocess.run(["security", "find-generic-password",
"-s", "Claude Code-credentials", "-w"], capture_output=True, text=True)
token = json.loads(r.stdout)["claudeAiOauth"]["accessToken"]
req = urllib.request.Request("https://api.anthropic.com/api/oauth/usage", headers={
"Authorization": f"Bearer {token}",
"anthropic-beta": "oauth-2025-04-20",
})
응답의 limits[] 에 5시간·주간·모델별 주간이 percent, severity(normal/warning/critical), resets_at 과 함께 온다. 문서화된 API 는 아니다. 그래서 60초 캐시를 두고 실패하면 Claude Code 가 ~/.claude.json 의 cachedUsageUtilization 에 마지막으로 받아둔 값으로 버틴다. 그때는 “(캐시)” 표시가 붙는다.
토큰은 트랜스크립트 합산이다. type: assistant 줄의 message.usage 에 input / output / cache_read / cache_creation 이 있다. 306MB 를 훑는 데 0.4초 — 줄마다 JSON 파싱을 하기 전에 b'"usage"' 바이트가 있는지만 먼저 보는 프리필터 덕이다. 스트리밍으로 같은 메시지가 여러 줄 남을 수 있어서 (requestId, message.id) 로 중복을 거른다. 이것도 60초 캐시.
한계도 있다. 트랜스크립트는 일정 기간 뒤 지워지므로 “전체 누계”는 못 낸다. 오늘과 7일만 보여준다.
실전에서 배운 것들
- 닫기 버튼에는 가드가 있어야 한다. 웹에서 pane 닫기를 만들면서 살아 있는 세션이 있으면 상태와 무관하게 거부하게 했는데, 이 가드가 없던 시절에 세션 3개를 날렸다. 하나는 대시보드 자기 자신, 둘은 검증 스크립트의
grep -w가@0을 잘못 집어서. 유휴 세션도 막는다 — 닫으면 세션이 끝나고--resume을 쳐야 하니까. - 정렬은 tmux 배치 순서로 고정한다. 상태로 재정렬하면 카드가 튀어서 어느 게 뭔지 헷갈린다. 상태는 색과 점으로만 알린다.
- 실행 중인 세션에 프롬프트를 넣는 공식 경로는 없다.
claude서브커맨드에도 없고, 세션 간 메시징은 세션 안에서만 된다.tmux send-keys -l이 유일한 경로다. 셸 pane 에 보내면 그 문장이 셸 명령이 되므로 세션 없는 pane 은 거부하고 승인 대기 중엔 화면이 선택지 메뉴라 글자가 그 선택을 눌러버리므로 역시 거부한다. - 입력 중엔 재렌더를 멈춘다. 2초마다 카드를 다시 그리는 구조에서 입력창에 포커스가 있을 때 목록을 다시 그리면 한글 조합이 깨진다.
- GUI 앱이 부르는 스크립트는 PATH 가 빈약하다. launchd 도, SwiftBar 도, Übersicht 도 로그인 셸을 안 거친다. 직접 실행하면 멀쩡한데 자동 실행에서만 빈 결과가 나오면
/opt/homebrew/bin이 없는 걸 먼저 의심한다. - 실행 중인 세션은 설정을 메모리에 들고 있다. 설정 디렉터리 경로를 바꿨더니 떠 있던 세션들의 상태줄이 조용히 사라지고 훅도 멎었다. 시작할 때 읽은 명령 경로를 그대로 쓰기 때문이다. 옛 경로로 심링크를 하나 놓아 두고 세션이 전부 재시작된 뒤 지운다.
- 클라우드에 올리면 안 된다. 데이터가 로컬 파일이라 서버리스에서 못 읽고, 억지로 밀어올리면 프롬프트 전문이 외부에 쌓인다. 예쁜 주소가 필요하면
/etc/hosts별칭이면 된다. 웹은0.0.0.0에 열려 있어 같은 와이파이에선 보인다는 것도 기억해 둬야 한다.
정리
| Before | After | |
|---|---|---|
| 승인 대기 발견 | 탭을 돌다가 우연히 | 메뉴바 ◆ + 웹 알림 |
| 완료 확인 | 세션마다 열어서 | 안 본 것만 ◉, 보면 자동으로 ○ |
| 마지막 요청 | 기억에 의존 | 카드·메뉴 둘째 줄 |
| 사용 한도 | /usage 를 쳐서 | 메뉴바 숫자 하나 |
| 계측 | 훅으로 직접 기록할 생각 | Claude Code 가 남기는 파일을 읽기만 |
결국 새로 만든 건 읽는 쪽뿐이다. Claude Code 는 세션 상태와 트랜스크립트와 사용량을 이미 파일로 남기고 있었고, 나는 그걸 창 밖으로 꺼냈을 뿐이다. 파이썬 파일 셋(스캐너·웹·한도)과 셸 스크립트 몇 개, SwiftBar 플러그인 하나. 전부 로컬이고 서버가 없다.
여러 세션을 돌리는 사람이라면 ~/.claude/sessions/ 부터 열어 보길 권한다. 생각보다 많은 게 이미 거기 있다.