◉CLAUDE LABEN
●2.1.289 — Claude Code 2.1.289(10月3日)で、複合シェルコマンドの deny/ask ルールと、コードブロックでの端末フリーズが修正されました●10/07 — Claude Desktop / Cowork の管理構成キー旧綴りの受付終了まで残り2日。締切後は fail-closed で読み込まれません●ENV — DISABLE_TELEMETRY を 0 にしても無効にならない、という報告(#89386)が出ています。真偽値の読み方が論点です●NEW — 11月30日に止まる claude-sonnet-4-5、置き換え前に請求の差分を確かめる●MODS — Claude Code 2.1.287 から Claude Mods が入りました。プラグインがより深い挙動を変えられるようになります●CONN — 同じ Connector を別アカウントで使い分けたいという要望(#27302)に260件超のコメント。仕事用と私用の分け方を考える題材です●2.1.289 — Claude Code 2.1.289(10月3日)で、複合シェルコマンドの deny/ask ルールと、コードブロックでの端末フリーズが修正されました●10/07 — Claude Desktop / Cowork の管理構成キー旧綴りの受付終了まで残り2日。締切後は fail-closed で読み込まれません●ENV — DISABLE_TELEMETRY を 0 にしても無効にならない、という報告(#89386)が出ています。真偽値の読み方が論点です●NEW — 11月30日に止まる claude-sonnet-4-5、置き換え前に請求の差分を確かめる●MODS — Claude Code 2.1.287 から Claude Mods が入りました。プラグインがより深い挙動を変えられるようになります●CONN — 同じ Connector を別アカウントで使い分けたいという要望(#27302)に260件超のコメント。仕事用と私用の分け方を考える題材です
記事一覧/Claude Code
⟐ Claude Code/2026-10-05中級

「コミットは私がします」と書いたのに git log に一行増えていた朝、指示を強めるのをやめました

Claude Code に「コミットしないで」と頼んだのにコミットされたとき、まず git reset --soft で戻し、permissions.deny と PreToolUse hook の二層で物理的に止める手順です。deny ルールが素通しする git -c の形と、hook の試し方を書き残します。

Claude Code260permissions.denyPreToolUse3hooks20git commitトラブルシューティング48

個人開発の壁紙アプリで、Firebase の導入方法を CocoaPods から Swift Package Manager へ切り替えていた週のことです。Podfile を外し、Package の依存を書き直し、ビルドを通すまでを Claude Code に任せ、CLAUDE.md の先頭には「コミットとプッシュは私が行います。差分は作業ツリーに残してください」と書いておりました。翌朝、ビルドの結果を見る前に何気なく git log を叩くと、覚えのない一行が増えていました。

コミットメッセージはきちんとしていて、変更も壊れていませんでした。けれど、履歴に刻む前に自分の目で差分を読むという手順が、私の手を経ずに終わっていたのです。胃のあたりが少し重くなりました。

同じ経験をされた方は少なくないようで、Claude Code のリポジトリには「明示的に禁止したのにコミットされた」という Issue が複数立ち、重複として閉じられてもまた開かれています。私も最初は CLAUDE.md の文言を太字にし、「絶対に」を足し、禁止の理由まで書き足しました。それでも数日後に同じことが起き、そこで考えを変えました。

最初にお伝えしたいのは、指示は読まれるものであって、効くものではないという線引きです。コミットを止めたいなら、止める機構を置くほうが早く、確実で、そして自分の気持ちも落ち着きます。戻し方、切り分け、deny ルール、hook、直らないときの順に書き残します。

症状の見分け方 — まず履歴を戻し、誰が刻んだかを確かめる

コミットされたこと自体は、変更が消えたわけではありませんので、落ち着いて戻せます。直前の一つなら、次の一行で差分を作業ツリーに戻しつつ履歴だけを取り消せます。

# 直前のコミットを取り消し、変更はステージに残す
git reset --soft HEAD~1
git status --short

次に、本当に Claude Code が刻んだのかを見ます。私の環境では Claude Code は私の git 設定で動きますので、author を見ても区別がつきません。手がかりになるのは、コミット日時とセッションの記録の突き合わせ、そして reflog です。

git log -3 --format='%h %ad %an | %s' --date=iso
git reflog -5

reflog に commit: の行があり、その時刻にセッションが動いていたなら、それが答えです。rebase や stash の自動操作で履歴が動いたように見えるだけのこともありますので、rebase や stash の行と見間違えないようにしています。

原因の切り分け — どの層が止めるはずだったか

ここで一度、何が何を止めるのかを棚卸ししました。Claude Code には「書いた指示」と「設定で与える権限」と「ツール実行前に挟む hook」の三つの層があり、それぞれ止められるものが違います。

層止めるもの止められないもの
CLAUDE.md の指示モデルの判断(読んで従う)解釈のずれ・長いセッションでの見落とし
permissions.denyルールに合う形のコマンド文同じ操作の別の書き方
PreToolUse hook自分のロジックで検査したコマンド文コマンド文に現れない操作
サンドボックスファイル・ネットワークへの到達そのもの——

私が頼っていたのは一番上の層だけでした。CLAUDE.md の文言は確実に読まれますが、「ビルドが通ったので区切りとして記録しておく」といった判断が差し込まれると、文言はその判断に負けることがあります。これはモデルの不具合というより、文章で渡した約束の性質なのだと、いまは受け止めております。

一方で、下の二層は判断に関わりません。コマンド文が条件に合えば、理由を問わず止まります。止めたい操作は、読ませるのではなく、通さないようにします。 この一文を CLAUDE.md の代わりに置くことにしました。

直し方① permissions.deny に一行足す

最初に足したのは deny ルールです。プロジェクトの .claude/settings.json(チームと共有しないなら .claude/settings.local.json)に書きます。

{
  "permissions": {
    "deny": [
      "Bash(git commit *)",
      "Bash(git push *)"
    ]
  }
}

これだけで git commit -m "..." は実行前に拒否され、Claude Code には拒否された旨が返ります。公式ドキュメントによると、deny と ask のルールは複合コマンドの一部にも当たりますので、cd ios && git commit -am wip のように前に cd が付いていても止まります。サブシェルや $() の中に書かれた場合も同様です。

ただ、私の環境ではこれで終わりませんでした。理由は、私自身の癖にありました。

サイトの運用では、環境に git の identity を残さないために git -c user.email=... -c user.name=... commit -m "..." という形でコマンドを書いております。CLAUDE.md にもその形で手順を載せていました。Claude Code はその手順を忠実に真似し、-c を挟んだ形でコミットしていたのです。Bash(git commit *) は「git の直後に commit が来る文」にしか当たりませんので、-c が間に入った文は素通しでした。

公式ドキュメントもこの点を隠していません。Bash(git push *) は git push origin main を止めるが、git -C . push origin main や git -c push.default=current push origin main は止めない、と表で示しています。deny ルールは「Claude がふだん書く形」を止めるものであって、プログラムそのものを囲う境界ではない——そう書いてあります。

なお Bash(command:git commit *) のように引数名で縛る書き方は、複合コマンドで回避できるため無視され、起動時に警告が出ます。私も一度書いて警告を見ました。

直し方② PreToolUse hook でコマンド文全体を読む

別の書き方まで含めて止めるには、コマンド文の全体を自分のロジックで検査する hook を置きます。PreToolUse はツールが実行される直前に動き、標準入力の JSON からコマンド文を取り出せます。

何を解決するかを先に書きますと、「git の後ろにいくつオプションが挟まっても、commit か push という語が来たら拒否する」という一点です。

#!/bin/bash
# .claude/hooks/no-commit.sh
# git <options…> commit / push をコマンド文全体から探して止める
COMMAND=$(jq -r '.tool_input.command // empty')
[ -z "$COMMAND" ] && exit 0
 
if echo "$COMMAND" | grep -Eq '(^|[^[:alnum:]_./-])git([[:space:]]+-[^[:space:]]+([[:space:]]+[^[:space:]-][^[:space:]]*)?)*[[:space:]]+(commit|push)([[:space:]]|$)'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "commit と push は人が行う運用です。差分は作業ツリーに残し、このコマンドは実行しないでください。"
    }
  }'
fi
exit 0

chmod +x .claude/hooks/no-commit.sh を忘れずに。設定側は matcher を Bash にして結びます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/no-commit.sh" }
        ]
      }
    ]
  }
}

なぜこう書くかですが、正規表現の中ほどにある ([[:space:]]+-[^[:space:]]+(...)?)* が「-c key=value や -C path のようなオプションの繰り返し」を飲み込む部分です。これがあるおかげで、git commit も git -c user.email=a@b.c commit も git -C . push も同じ一本で捕まります。permissionDecisionReason の文は Claude Code にそのまま渡りますので、拒否の理由と次の振る舞いを一文で書いておくと、同じ試みを繰り返されにくくなりました。

hook は Claude Code を起こさずに手元で試せます。標準入力に JSON を流せばよいだけです。

for c in 'git commit -m "fix"' \
         'git -c user.email=a@b.c -c user.name=x commit -m "x"' \
         'cd ios && git commit -am wip' \
         'git -C . push origin main' \
         'git status && git diff --stat' \
         'git log --oneline -3'; do
  printf '%-55s -> ' "$c"
  jq -n --arg c "$c" '{tool_name:"Bash",tool_input:{command:$c}}' \
    | .claude/hooks/no-commit.sh \
    | jq -r '.hookSpecificOutput.permissionDecision // "pass"'
done

私の手元では、上の四つが deny、下の二つが pass になりました。一つ正直に書き添えますと、echo "git commit は私が" のように文字列の中に書いただけでも deny になります。hook はコマンド文を読んでいるだけで、意味は読んでいないのです。私はこれを欠点ではなく、安全側に倒れる誤りとして受け入れています。

ここまでが二層目と三層目です。bash -c '...' の中に書かれた形も hook は文として読みますので止まりますが、コマンド文に現れない操作——たとえばスクリプトファイルを書いてから実行する——は読めません。そこまで囲いたい場合は、表の一番下のサンドボックスの出番になります。

直らないときの次の一手

deny と hook を置いても止まらないときは、順番に次を見ています。

  1. /hooks で登録が見えているか。 設定ファイルの置き場所が違う、JSON の括弧が一つ多い、というのがいちばん多い原因でした。
  2. 実行権限と jq。 chmod +x が抜けていないか、which jq が通るか。jq が無いと hook は何も出さずに終わり、結果として通してしまいます。
  3. 新しいセッションでの再確認。 設定を変えた直後は、私は一度セッションを閉じて開き直してから確かめるようにしています。
  4. 別の形で書かれていないか。 /usr/bin/git や sh -c の形なら、正規表現の先頭の文字クラスを見直すか、サンドボックスへ進みます。

hook 自体の切り分けに踏み込むときは、Claude Code Hooks の本番デバッグ術 — ログが足りない時、どこから切り分けるか に手順を書いております。

差分は Claude に、履歴は私に

戻って考えますと、私が欲しかったのは「コミットしないでくれる Claude」ではなく、「コミットできない作業環境」でした。前者は毎回の運に、後者は一度の設定に依ります。

差分を作るのは Claude、履歴に刻むのは私。 この線引きを置いてから、朝の git log を開くときの胃の重さが消えました。差分を読んで自分の手でコミットする数分は、任せる範囲を広げるための、むしろ安い代価なのだと感じています。

まずは .claude/settings.json に Bash(git commit *) の一行だけを足し、翌朝の git log を見るところから始めていただければと思います。素通しされる形が見つかったら、そのときに hook を足せばよいのです。hook をどのイベントに置き、何を返させるかの判断は、Claude Code Hooks — 中核8フックの設計判断と、33イベントに増えた今の歩き方 でまとめて書いております。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⟐ Claude Code2026-09-10
「Not signed in to the Cloud gateway」で全リクエストが止まる朝に、設定より先に版を見ます
Claude Code 2.1.265 でゲートウェイ・プロキシ構成が全リクエスト失敗した回帰を入口に、認証の入力面を一枚に並べて「設定と版のどちらが変わったか」を数秒で切り分ける小さな手順をまとめます。
⟐ Claude Code2026-09-04
~/.claude.json の巻き戻しは v2.1.259 で直りました。消えた設定は自分で戻します
同時に走らせたセッションが ~/.claude.json を互いに巻き戻す不具合が v2.1.259 で修正されました。消えた信頼設定と MCP を洗い出して戻すまでを、キー集合の差分を取る小さなスクリプトとあわせて書き残します。
⟐ Claude Code2026-09-02
モデル切り替えの記録と歯止めを、フック2本で作る
PreModelSwitch と PostModelSwitch を使い、無人実行中のモデル切り替えを記録するフックと、約束していない切り替えだけを止めるフックをゼロから作ります。実測値つきの手順です。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます