📱

Claude Code の許可通知をスマホに飛ばして、デスクから離れられるようにした

に公開

はじめに

コーヒーを淹れて戻ったら、Claude Code が 15 分間止まっていた。

Permission の確認待ち。allowlist に追加すれば許可を減らせますが、未知のコマンドやファイル書き込みまで全許可するのはリスクがあります。かといって、ターミナルにずっと張り付いているのも現実的ではない。

そこで Claude Code の PermissionRequest フックと ntfy.sh を組み合わせて、スマホから Allow / Deny を選べる仕組み を作りました。インストーラ付きで OSS 化したので、3 分で導入できます。

https://github.com/coa00/claude-push

課題:ターミナルに張り付くか、全許可するかの二択

Claude Code でコード生成や修正を任せていると、こんな場面に遭遇します。

Claude wants to run: rm -rf dist && npm run build
Allow? (y/n)

この確認が出るたびにターミナルに戻って y を押す。作業に集中していると見逃して、Claude Code が数分間止まったまま——ということが頻繁に起きていました。

対処法 メリット デメリット
毎回ターミナルで確認 安全 席を離れられない
allowlist で全許可 未知のコマンドも通る
スマホに通知して承認 離席OK+安全 仕組みの構築が必要

3 つ目を実現するのが今回の claude-push です。

元ネタ

konsti-web/claude_push という先行プロジェクトがありますが、Windows + PowerShell + キーストローク注入方式で、macOS / Linux では動きません。同じ課題を感じていたので、bash + PermissionRequest フック で一から作り直しました。

仕組み:PermissionRequest フック + ntfy.sh

Claude Code には Hooks という仕組みがあり、特定のイベントで外部スクリプトを実行できます。PermissionRequest イベントにフックを登録すると、許可画面の 代わりに 自前のスクリプトで判断を返せます。

全体の流れはこうです。

Claude Code が Permission を要求
  → フックスクリプトが起動
    → ntfy.sh に Allow/Deny ボタン付き通知を送信
      → スマホで通知を受け取り、ボタンをタップ
        → ntfy.sh の SSE でレスポンスを受信
          → フックが allow/deny の JSON を返す
            → Claude Code が続行 or 中止

ntfy.sh は HTTP ベースの無料通知サービスです。アカウント登録不要で、トピック名さえ知っていれば誰でも通知を送受信できます。Pushover や LINE Notify と違い、サインアップなしで curl 1 発で通知が飛ぶ手軽さが、フックスクリプトとの相性が良いと感じて選びました。

実装のポイント

フックスクリプト本体は約 60 行の bash です。ポイントを 3 つ紹介します。

1. ntfy.sh の HTTP Action でボタンを実現

ntfy.sh は通知に Action ボタン を付けられます。http アクション を使い、ボタンタップ時に別トピックへ POST を飛ばす構成にしています。

curl -s -H "Content-Type: application/json" \
  -d "$(jq -n \
    --arg topic "$TOPIC" \
    --arg title "[$PROJECT] $TOOL_NAME" \
    --arg message "$TOOL_INPUT" \
    --arg allow_url "https://ntfy.sh/${RESPONSE_TOPIC}" \
    --arg allow_body "allow|$REQ_ID" \
    --arg deny_url "https://ntfy.sh/${RESPONSE_TOPIC}" \
    --arg deny_body "deny|$REQ_ID" \
    '{
      topic: $topic, title: $title, message: $message,
      priority: 4, tags: ["lock"],
      actions: [
        {action:"http",label:"Allow",url:$allow_url,method:"POST",body:$allow_body},
        {action:"http",label:"Deny",url:$deny_url,method:"POST",body:$deny_body}
      ]
    }')" "https://ntfy.sh/"

通知トピックとレスポンストピックを分けているのがポイントです。通知の受信と応答の受信を別チャネルにすることで、SSE のストリームが自分の通知で汚染されるのを防いでいます。

2. SSE + リクエスト ID でレスポンスを特定

フックは通知を送ったあと、ntfy.sh の SSE エンドポイントでレスポンスを待ちます。

REQ_ID="$(date +%s)-$$"

while IFS= read -r line; do
  if [[ "$line" == data:* ]]; then
    DATA="${line#data: }"
    MSG=$(echo "$DATA" | jq -r '.message // empty' 2>/dev/null)
    if [[ "$MSG" == *"|$REQ_ID" ]]; then
      DECISION="${MSG%%|*}"
      break
    fi
  fi
done < <(curl -s -N --max-time "$WAIT_TIMEOUT" \
  -H "Accept: text/event-stream" \
  "https://ntfy.sh/${RESPONSE_TOPIC}/sse")

REQ_ID(タイムスタンプ + PID)を通知の body に埋め込み、レスポンスにも含めることで、複数の通知が同時に飛んでも正しい応答を特定 できます。これがないと、前の通知への応答を今の通知の結果として拾ってしまうことがあります。

3. タイムアウト時はフォールバック

--max-time でタイムアウトさせ、応答がなかった場合は 何も出力せずに exit 0 します。

if [ "$DECISION" = "allow" ]; then
  jq -n '{hookSpecificOutput:{...decision:{behavior:"allow"}}}'
elif [ "$DECISION" = "deny" ]; then
  jq -n '{hookSpecificOutput:{...decision:{behavior:"deny"}}}'
fi
# Timeout: no output → falls back to interactive prompt

Claude Code のフック仕様では、出力なし + exit 0 は「フックが判断しなかった」と解釈され、通常のターミナルプロンプトにフォールバックします。スマホを見ていなくても、タイムアウト後にターミナルで対応できるので安全です。

導入手順

前提

  • macOS または Linux
  • bash, jq, curl がインストール済み
  • スマホに ntfy アプリ がインストール済み

インストール

git clone https://github.com/coa00/claude-push.git
cd claude-push
bash install.sh

インストーラが対話形式で以下を行います。

  1. 依存チェックjqcurl の存在確認
  2. トピック名の入力 — 未入力ならランダム生成(claude-push-a1b2c3d4
  3. 設定ファイルの作成~/.config/claude-push/config
  4. フックの配置~/.local/share/claude-push/hooks/claude-push.sh
  5. Claude 設定へのフック登録~/.claude/settings.jsonjq で安全にマージ
  6. テスト通知の送信 — スマホに届くか確認

インストール後、ntfy アプリでトピックを購読すれば完了です。

設定の変更

~/.config/claude-push/config を編集するだけです。再インストールは不要。

# トピック名(ntfy.sh のチャネル名。共有シークレットとして機能)
CLAUDE_PUSH_TOPIC="my-unique-topic"

# タイムアウト秒数(この時間内に応答がなければターミナルにフォールバック)
CLAUDE_PUSH_TIMEOUT=90

動作確認

# Allow/Deny ボタン付きテスト通知を送信
bash scripts/test.sh test-notify

# インストール状態をチェック
bash scripts/test.sh status

status コマンドで Config / Hook / Settings / 依存の 4 項目を確認できます。

=== claude-push status ===
[OK] Config: ~/.config/claude-push/config
     Topic: my-unique-topic
     Timeout: 90s
[OK] Hook: ~/.local/share/claude-push/hooks/claude-push.sh
[OK] Settings: hook registered in ~/.claude/settings.json
[OK] Dependency: jq
[OK] Dependency: curl

アンインストール

bash uninstall.sh

Claude 設定からフックを削除し、インストールしたファイルをクリーンに除去します。

使い方のイメージ

導入後の日常はこんな感じです。

  1. Claude Code にリファクタリングを指示して、席を立つ
  2. 数分後、スマホに [myproject] Bash: npm run build の通知が来る
  3. 内容を確認して Allow をタップ
  4. デスクに戻ったら、ビルドまで終わっている

ミーティング中や移動中でも、スマホさえあれば Claude Code を止めずに済みます。

Before / After

Before After
許可の確認方法 ターミナルで y/n スマホで Allow/Deny
離席中の動作 Claude Code が停止して待機 スマホ通知で対応可能
タイムアウト なし(永遠に待つ) 90 秒後にターミナルにフォールバック
セキュリティ allowlist か手動確認 通知ごとに個別判断
導入コスト bash install.sh で 3 分

セキュリティに関する注意

ntfy.sh のトピック名は 共有シークレット として機能します。トピック名を知っている人は誰でも通知を送ったり、応答を偽装できます。

  • トピック名は推測されにくいランダムな文字列にする(インストーラのデフォルトはランダム生成)
  • より厳密にしたい場合は ntfy.sh のアクセス制御 を設定する
  • 重要な操作(rm -rf など)は allowlist で明示的にブロックしておくのが安全

まとめ

「ターミナルに張り付くか、全許可するか」の二択だった Claude Code の Permission 運用に、スマホ承認 という第三の選択肢を作りました。

仕組み自体は bash + ntfy.sh の HTTP Action + SSE という枯れた技術の組み合わせで、フック本体は 60 行程度です。Claude Code の Hooks API がよくできていて、PermissionRequest に JSON を返すだけで許可/拒否を制御できるのが大きかったです。

効果についてはこれから検証していく段階です。1 日のうちターミナルに戻る回数がどれだけ減るか、Permission 待ちによる停止時間がどう変わるかを追っていく予定です。少なくとも「コーヒーを淹れに行っている間に Claude Code が止まっている」は解消できそうです。

あなたは Claude Code の Permission をどう管理していますか? allowlist 派も手動確認派も、一度試してみてもらえると嬉しいです。

https://github.com/coa00/claude-push

参考

GitHubで編集を提案
PURPM MEDIA LAB Tech blog

Discussion