주맨의 개발노트

Claude Code Hook으로 작업 완료 알림 받기 본문

AI

Claude Code Hook으로 작업 완료 알림 받기

JooMan 2026. 3. 31. 20:08

들어가며

Claude Code로 작업을 맡겨두고 다른 일을 하다 보면 "언제 끝났지?" 하고 화면을 계속 확인하게 됩니다. 특히 여러 프로젝트를 동시에 돌릴 때는 어느 인스턴스가 완료됐는지도 알기 어렵습니다. Android Studio 플러그인, VS Code 터미널, 일반 터미널까지 열어두면 상황은 더 복잡해집니다.

저도 처음에는 그냥 터미널 탭을 왔다 갔다 하면서 확인했습니다. 그런데 이게 생각보다 흐름을 많이 끊었습니다. 아직 안 끝난 작업을 확인하러 들어갔다가 다시 원래 하던 일로 돌아오는 일이 반복됐고, 어떤 때는 Claude Code가 이미 제 응답을 기다리고 있는데도 한참 뒤에 알아차리기도 했습니다.

그래서 Claude Code의 Hook 시스템으로 macOS 네이티브 알림을 붙였습니다. 제가 실제로 쓰고 있는 설정에는 작업 완료와 사용자 응답 요청을 서로 다른 소리로 구분하는 Hook이 들어가 있고, 알림 메시지에는 프로젝트명과 시각도 함께 표시되도록 되어 있습니다. 이 글은 그 실제 설정과, 그 설정을 쓰면서 느낀 사용 흐름의 변화를 함께 정리한 기록입니다.

Hook을 붙이게 된 이유

Claude Code를 백그라운드 작업자처럼 쓰기 시작하면서 가장 먼저 부딪힌 건 완료 시점을 놓치는 문제였습니다. 작업이 30초 걸릴지 5분 걸릴지 매번 다르다 보니, 결국 사람 쪽에서 계속 상태를 확인하게 됩니다. 자동화 도구를 써 놓고 다시 수동 모니터링으로 돌아가는 셈이었습니다.

제가 원한 건 거창한 대시보드가 아니었습니다. 그냥 "끝났다"와 "지금 제가 답해야 한다"를 확실히 구분해서 알려주는 신호면 충분했습니다. Hook은 딱 그 지점에 잘 맞았습니다. Claude Code가 직접 뭔가 복잡한 UI를 제공하는 게 아니라, 특정 이벤트가 발생했을 때 제가 원하는 쉘 명령어를 붙일 수 있기 때문입니다.

Hook이 동작하는 방식

Hook은 Claude Code의 특정 이벤트가 발생할 때 쉘 명령어를 자동으로 실행하는 메커니즘입니다. Claude가 직접 알림을 띄우는 게 아니라, 이벤트가 발생하는 순간 지정해 둔 명령어가 실행되는 구조입니다.

1 — 이벤트 발생 Claude Code가 응답을 마치거나 사용자 입력을 기다림
2 — Hook 트리거 settings.json에 등록된 명령어 실행
3 — 알림 표시 macOS 네이티브 알림 팝업 + 소리 재생

이 구조가 좋았던 이유는 설정을 한 군데만 바꾸면 된다는 점입니다. Hook은 ~/.claude/settings.json에 등록하므로 Android Studio 플러그인, VS Code 터미널, 일반 터미널 등 어디서 Claude Code를 띄우든 동일하게 동작합니다. 한 환경에서만 따로 맞춰야 하는 종류의 설정이 아니라는 점이 꽤 편했습니다.

실제로는 두 이벤트만 있어도 충분했다

Claude Code는 다양한 이벤트에 Hook을 걸 수 있지만, 제 현재 설정 파일에 들어 있는 알림 Hook은 두 가지뿐입니다. 작업이 끝났다는 신호와, 지금 제가 확인해야 한다는 신호입니다. 제 경험상 이 둘만 분리해도 사용 흐름은 꽤 달라졌습니다.

Stop

작업 완료 — Claude가 응답을 마쳤을 때

Claude가 응답 생성을 완전히 끝내고 사용자 입력을 기다리는 상태가 될 때 트리거됩니다. 코드 작성, 파일 수정, 분석 등 모든 작업이 끝난 시점입니다. "이제 확인하러 가도 됩니다"라는 신호로 쓰기 좋습니다.

Noti

응답 요청 — Claude가 사용자 입력을 기다릴 때

Claude가 작업 중 사용자 확인이 필요한 상황에 트리거됩니다. Plan 모드에서 승인 대기, 파일 수정·명령 실행 권한 요청, 선택지 제시 등이 해당됩니다. "지금 당장 확인해야 합니다"라는 신호로 쓰기 좋습니다.

참고로 Hook이 지원하는 전체 이벤트는 Stop, Notification 외에도 PreToolUse, PostToolUse, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, SubagentStop가 있습니다. 저도 처음에는 이것저것 다 붙여볼까 생각했지만, 실제로 계속 유지하게 되는 건 단순한 설정이었습니다. 알림처럼 즉시 체감되는 자동화부터 붙이는 편이 훨씬 낫다고 느꼈습니다.

macOS 알림 명령어

macOS에서 네이티브 알림을 띄우는 가장 간단한 방법은 osascript입니다. 추가 설치가 필요 없다는 점이 좋았습니다. 이 작업을 위해 별도 앱이나 메뉴바 유틸리티를 깔고 싶지는 않았기 때문입니다. 기본 제공 기능만으로 충분했습니다.

기본 형태 Shell
osascript -e "display notification \"메시지\" with title \"제목\" subtitle \"부제목\" sound name \"Glass\""

알림에 표시할 수 있는 필드는 네 가지입니다.

필드 설명 예시
notification 알림 본문. 가장 눈에 잘 띄는 텍스트. my-android-app 완료
title 앱 이름 위치에 표시되는 굵은 텍스트. Claude Code
subtitle title 아래 작게 표시되는 보조 텍스트. 14:32
sound name 알림 소리. /System/Library/Sounds/의 파일명. Glass, Ping

프로젝트명과 시각 자동 삽입

제 현재 설정에서 눈에 띄는 포인트는 프로젝트명을 자동으로 넣는 부분입니다. 실제 명령어에 $(basename $PWD)$(date +%H:%M)이 들어가 있어서, 알림에 현재 작업 디렉토리명과 시각이 함께 표시됩니다. 여러 Claude Code 인스턴스를 같이 띄울 때는 알림이 왔다는 사실보다 어느 프로젝트에서 온 알림인지가 더 중요하다고 느꼈는데, 저는 이 방식이 그 문제를 가장 단순하게 풀어준다고 봤습니다.

프로젝트명 + 시각 포함 Shell
osascript -e "display notification \"$(basename $PWD) 완료\" with title \"Claude Code\" subtitle \"$(date +%H:%M)\" sound name \"Glass\""

$PWD는 Hook이 실행되는 시점의 작업 디렉토리를 반환합니다. Claude Code가 실행된 폴더가 곧 $PWD가 되므로, 같은 명령어를 어떤 프로젝트에서든 그대로 쓸 수 있습니다. 따로 프로젝트명을 하드코딩할 필요가 없어서 실제 운용할 때 훨씬 덜 번거롭습니다.

단순 알림

알림이 울려도 화면을 봐야 어느 프로젝트인지 알 수 있습니다. 인스턴스가 많을수록 혼란스러워집니다.

프로젝트명 포함 알림

알림 배너에 my-android-app 완료처럼 프로젝트명이 표시되어 화면을 보지 않아도 파악할 수 있습니다.

두 가지 Hook 설정

제 실제 설정에서는 StopGlass, NotificationPing을 연결해 두었습니다. 그 이유는 제 사용 경험 때문입니다. 화면을 보고 있지 않을 때는 텍스트보다 소리가 먼저 들어오는데, 저는 Glass 소리(맑고 길게)를 작업 완료 신호로, Ping 소리(짧고 날카롭게)를 응답 요청 신호로 두는 쪽이 가장 직관적이었습니다.

Stop — 작업 완료 알림

🖥️
Terminal14:32
Claude Code
my-android-app 완료

🔔 Glass 사운드 재생

Notification — 응답 요청 알림

🖥️
Terminal14:33
Claude Code ⚠️
my-android-app — 응답이 필요합니다

🔔 Ping 사운드 재생

제가 현재 쓰는 settings.json 설정

~/.claude/settings.jsonhooks 키를 추가하면 됩니다. 기존 설정(플러그인, statusLine 등)은 그대로 두고 새 키만 넣으면 됩니다. 아래는 제가 지금 그대로 쓰고 있는 형태입니다.

~/.claude/settings.json JSON
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e \"display notification \\\"$(basename $PWD) 완료\\\" with title \\\"Claude Code\\\" subtitle \\\"$(date +%H:%M)\\\" sound name \\\"Glass\\\"\""
          }
        ]
      }
    ],
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e \"display notification \\\"$(basename $PWD) — 응답이 필요합니다\\\" with title \\\"Claude Code ⚠️\\\" subtitle \\\"$(date +%H:%M)\\\" sound name \\\"Ping\\\"\""
          }
        ]
      }
    ]
  }
}

설정 파일을 저장한 후 Claude Code를 재시작하면 적용됩니다. Claude Code는 시작 시 settings.json을 읽기 때문에 실행 중 수정해도 재시작이 필요합니다. 처음에는 저장만 하면 바로 반영될 줄 알았는데, 이 부분은 한 번 놓치기 쉬웠습니다.

다만 실제 설정을 그대로 적으면 지금처럼 "command" 한 줄이 길어질 수밖에 없습니다. JSON 문자열 자체는 예쁘게 여러 줄로 나누기 어렵기 때문입니다. 가독성을 더 챙기고 싶다면 알림 명령어를 별도 쉘 스크립트로 빼는 쪽이 낫습니다.

가독성을 우선하면 스크립트로 분리하는 편이 낫다

예를 들어 settings.json에서는 스크립트만 호출하고, 실제 osascript 명령은 별도 파일로 분리할 수 있습니다. 이렇게 하면 설정 파일은 짧아지고, 알림 문구를 나중에 수정할 때도 훨씬 편합니다.

~/.claude/settings.json JSON
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify-stop.sh"
          }
        ]
      }
    ],
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify-input.sh"
          }
        ]
      }
    ]
  }
}
~/.claude/hooks/notify-stop.sh Shell
osascript -e "display notification \"$(basename $PWD) 완료\" with title \"Claude Code\" subtitle \"$(date +%H:%M)\" sound name \"Glass\""
~/.claude/hooks/notify-input.sh Shell
osascript -e "display notification \"$(basename $PWD) — 응답이 필요합니다\" with title \"Claude Code ⚠️\" subtitle \"$(date +%H:%M)\" sound name \"Ping\""

첫 실행 시 알림 권한 허용

처음 Hook이 실행될 때 macOS가 Terminal(또는 iTerm2)의 알림 권한을 요청합니다. 허용을 눌러야 이후 알림이 표시됩니다. 저는 처음에 "명령어는 맞는데 왜 알림이 안 뜨지?" 하고 잠깐 헷갈렸는데, 원인은 대부분 여기였습니다. 권한을 실수로 거부했거나 나중에 변경하고 싶다면 시스템 설정 → 알림 → Terminal에서 조정할 수 있습니다.

배너

배너 방식 권장

화면 오른쪽 상단에 잠깐 떴다가 자동으로 사라집니다. 알림 센터에도 기록이 남아 나중에 확인할 수 있습니다. 작업에 방해가 적으면서도 눈에 잘 띕니다.

알림
센터

알림 기록 확인

화면 오른쪽 상단 시계를 클릭하면 지나간 알림을 모두 볼 수 있습니다. 여러 인스턴스가 순서대로 완료됐을 때 타임라인을 확인하는 용도로 유용합니다.

사운드는 이렇게 골랐다

/System/Library/Sounds/에 있는 파일이라면 어떤 소리도 쓸 수 있습니다. sound name에는 확장자 없이 파일명만 적으면 됩니다. 실제 제 설정 파일에는 GlassPing이 들어 있습니다. 아래 표의 다른 항목은 선택 가능한 예시들이고, 왜 저는 이 조합을 쓰고 있는지도 함께 정리해봤습니다.

사운드 특징 추천 용도
Glass 맑고 길게 울리는 종소리 작업 완료 (Stop)
Ping 짧고 날카로운 금속음 응답 요청 (Notification)
Purr 부드럽고 낮은 진동음 조용한 환경에서 작업 완료
Funk 묵직하고 낮은 타격음 오류나 주의가 필요한 경우
Tink 아주 짧고 가벼운 틱 소리 빈번한 이벤트에서 덜 거슬리게

정리하며

이 설정을 붙이고 나서 가장 크게 달라진 건 Claude Code를 덜 감시하게 됐다는 점입니다. 예전에는 일이 끝났는지 확인하려고 제가 먼저 Claude Code를 들여다봤다면, 지금은 정말 필요한 순간에만 알림이 저를 부릅니다. 작은 차이 같지만 집중이 꽤 덜 끊깁니다.

정리하면, 제 실제 설정 파일에는 StopNotification 두 이벤트만 들어 있고, 각각 GlassPing 소리가 연결되어 있습니다. 또 basename $PWD를 써서 프로젝트명이 자동으로 들어가게 해 두었습니다. 여기서부터는 사실 설명이 아니라 제 경험인데, 저는 이 정도만으로도 Claude Code를 한결 덜 감시하게 됐습니다. 특히 여러 작업을 같이 돌릴 때 체감이 더 컸습니다.

핵심 정리
  • 제가 Hook을 붙인 이유는 Claude Code를 덜 감시하고 싶어서였습니다. 완료 여부를 확인하러 직접 들어가는 흐름이 줄어들면 작업 집중이 덜 끊깁니다.
  • Stop은 작업 완료, Notification은 응답 요청입니다. 두 이벤트만 나눠도 실제 사용성은 꽤 좋아집니다.
  • basename $PWD로 어느 프로젝트인지 자동 표시합니다. 이 부분은 실제 설정 명령어에도 들어 있고, 여러 인스턴스를 같이 띄울 때 특히 유용했습니다.
  • 소리 구분은 생각보다 효과가 큽니다. 실제 설정은 Glass와 Ping을 쓰고 있고, 저는 이 조합이 가장 직관적이었습니다.
  • 설정은 단순해야 오래 갑니다. 이건 제 결론입니다. 여러 이벤트를 다 붙이기보다, 먼저 체감이 큰 알림 자동화부터 두는 편이 유지하기 쉬웠습니다.
Comments