CLAUDE LABEN
2.1.269 — 日本語や中国語のように語間をスペースで区切らない言語で、プロンプト候補が落ちてしまう不具合が直りました。日本語で使っている方に直接効く修正ですKB5124008 — Windows 11 に9月の累積更新を当てると、Cowork がホスト側のフォルダをまったくマウントできなくなる報告が続いています。VM は起動するため、原因の切り分けが難しい形ですPUSH — クラウドや Cowork のセッションからの git push が、GitHub へ届く前にプロキシで拒まれる報告があります。clone は通るので、トークンの権限問題と読み違えやすいところですNEW — 資料を足したのに Projects の答えが浅くなる。残す知識ファイルを決める三つの問いを書きましたTOKENS — PDF を送る前に費用を見積もりたくても、CountTokens はドキュメント入力に対応していません。ページ数から概算するか、テキストを抜いて数えるかの二択ですCLEANUP — 「片付けて」と頼む前に、読み取りだけで足りる作業と、書き込みを許す作業を分けておく。フォルダを渡す順番は、消えてからでは決め直せません2.1.269 — 日本語や中国語のように語間をスペースで区切らない言語で、プロンプト候補が落ちてしまう不具合が直りました。日本語で使っている方に直接効く修正ですKB5124008 — Windows 11 に9月の累積更新を当てると、Cowork がホスト側のフォルダをまったくマウントできなくなる報告が続いています。VM は起動するため、原因の切り分けが難しい形ですPUSH — クラウドや Cowork のセッションからの git push が、GitHub へ届く前にプロキシで拒まれる報告があります。clone は通るので、トークンの権限問題と読み違えやすいところですNEW — 資料を足したのに Projects の答えが浅くなる。残す知識ファイルを決める三つの問いを書きましたTOKENS — PDF を送る前に費用を見積もりたくても、CountTokens はドキュメント入力に対応していません。ページ数から概算するか、テキストを抜いて数えるかの二択ですCLEANUP — 「片付けて」と頼む前に、読み取りだけで足りる作業と、書き込みを許す作業を分けておく。フォルダを渡す順番は、消えてからでは決め直せません
記事一覧/Claude Code
Claude Code/2026-07-30上級

作業ルートを途中で足したとき、無視規則と deny はどこまで届くのか — DirectoryAdded で境界を測り直す

セッション途中で作業ルートを追加すると、無視規則も deny パターンもそのままでは新しいルートに届きません。git check-ignore と照合器の比較で境界を実測し、DirectoryAdded から呼ぶ差分監査スクリプトの実行結果まで記録しました。

Claude Code254hooks18モノレポセキュリティ20運用設計25

プレミアム記事

セッションを止めずに /add-dir でもう一つのリポジトリを足したのは、iOS 側のビルド設定を並べて確認したかったからでした。

個人開発でアプリ側のリポジトリとサイト側のリポジトリを行き来していると、こういう横断はどうしても出てきます。

作業自体はすぐ終わりました。気になったのは、そのあとです。

普段どおり秘匿ファイルの棚卸しを走らせたところ、件数が変わっていませんでした。追加したルートには、設定ファイルが何本も入っているはずなのに。

数分かけて追い、原因は単純でした。棚卸しの起点が最初のルートのままだったのです。ルートを増やすという操作は、そこにぶら下がる前提まで一緒に運んではくれません。

セッションは正常。エラーもゼロ。ただ、検査の網だけが古いところに張られたままでした。

追加したルートは、既存ルートの子ではない

/add-dir や SDK の register_repo_root でルートを足すと、感覚としては作業範囲が「広がった」ように見えます。

実際に起きているのは、独立したルートがもう一本並んだ状態です。親子ではなく、並列。

この違いが効いてくるのが無視規則です。git の除外規則は、リポジトリの外側には適用されません。当たり前の仕様ですが、ルートが二本になった瞬間に、その当たり前が運用上の穴になります。

同じ構造を手元で作って確かめました。root_a には secrets/ を無視する .gitignore を置き、root_b には置いていません。中身はどちらも同じ secrets/prod.env です。

# root_a 側: .gitignore が効いている
$ git -C root_a check-ignore -v secrets/prod.env
.gitignore:1:secrets/	secrets/prod.env
 
# root_a から root_b のファイルを判定させようとすると、そもそも受け付けない
$ cd root_a && git check-ignore -v ../root_b/secrets/prod.env
fatal: ../root_b/secrets/prod.env: '../root_b/secrets/prod.env' is outside repository
 
# root_b 側では、同じパスが無視対象にならない
$ git -C root_b check-ignore -v secrets/prod.env
(出力なし・終了コード 1)

git status まで見ると、差は目に見える形で出ます。

$ git -C root_a status --porcelain
A  .gitignore
A  src/app.ts
 
$ git -C root_b status --porcelain
A  secrets/prod.env 追加ルート側では staged になる
A  src/app.ts

root_a では secrets/prod.env が一覧にすら現れず、root_b では素通しで staged まで進んでいます。

つまり、追加ルートに対する判定は、必ず追加ルートの中で git を回さないと正しい答えになりません。親ルートから相対パスで覗きにいく実装は、静かに間違った「安全」を返します。

deny パターンは、どの照合器で読まれるかで意味が変わる

無視規則の次に確認したのが permissions.deny です。

こちらは git とは別系統で、パターン文字列の照合になります。ここで前提が一つ崩れました。同じパターンでも、照合器が違えば結果が変わります。

代表的な三つ、fnmatchPath.match・glob と同じ ** セマンティクスを並べて、同じパスに当ててみました。

import fnmatch, re
from pathlib import PurePosixPath
 
def m_glob(p, pat):
    """glob と同じ ** セマンティクス(0個以上のディレクトリ)を再現する"""
    rx = (re.escape(pat)
          .replace(r"\*\*/", "(?:[^/]+/)*")
          .replace(r"\*\*", ".*")
          .replace(r"\*", "[^/]*")
          .replace(r"\?", "[^/]"))
    return re.fullmatch(rx, p) is not None
 
for pat in ["*.env", "**/*.env", "config/*.env", "**/config/*.env"]:
    for p in ["config/prod.env", "ios/Config/secrets.env",
              ".vscode/settings.json", "scripts/build.sh"]:
        print(pat, p,
              fnmatch.fnmatch(p, pat),
              PurePosixPath(p).match(pat),
              m_glob(p, pat))

実行して出た結果を表にしたものが以下です。

パターンパスfnmatchPath.matchglob(**)
*.envconfig/prod.envTrueTrueFalse
*.envios/Config/secrets.envTrueTrueFalse
**/*.envconfig/prod.envTrueTrueTrue
**/*.envios/Config/secrets.envTrueTrueTrue
config/*.envconfig/prod.envTrueTrueTrue
config/*.envios/Config/secrets.envFalseFalseFalse
**/config/*.envconfig/prod.envFalseFalseTrue
**/config/*.envios/Config/secrets.envFalseFalseFalse

読み方が二か所あります。

一つ目は *.env の行です。fnmatchPath.match では config/prod.env に当たっています。* がスラッシュを越えているのです。glob の感覚で「直下だけを指定したつもり」で書くと、意図より広く当たります。

二つ目は **/config/*.env の行です。glob では当たるのに、fnmatchPath.match では外れます。**/ が「0個以上のディレクトリ」ではなく「何かしらのディレクトリが1段以上」として読まれるためで、ルート直下の config/ が抜け落ちます。

ここが、私が事前に予想していたのと逆でした。**/ を付けるほど網が広がるつもりでいたのに、照合器によっては直下を取りこぼす方向に働きます。しかも取りこぼす対象が、追加ルートの直下という、いちばん設定ファイルが集まる場所でした。

ルートが一本だった頃は、この差に気づく機会がありません。パスの階層構造が一つしかないので、どの書き方でも結果が揃ってしまうからです。ルートを足して階層の浅い木が増えたときに、初めて表面化します。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
追加ルートの無視規則が親ルートへ合成されない挙動を git check-ignore で実証(同名の secrets/ が片方は無視され、片方は staged になる)
同じ deny パターンが照合器ごとに割れる比較表 — `*.env` はスラッシュを越えて当たり、`**/config/*.env` はルート直下を取りこぼす
DirectoryAdded から呼ぶ約70行の差分監査スクリプトと、その実行出力(未カバー2件・大小文字違い1件・パス衝突2件を検出)
2,882ファイルの追加ルートで計測したフック所要 21.8 ミリ秒と、差分監査が再走査より速くならなかった理由(差は 1% 未満)
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

Claude Code2026-08-16
Todo ツールが既定オフになった Claude Code で、多段作業の進捗をどう追うか
Claude Code v2.1.233 以降、新しいモデルでは TodoWrite と Task 系ツールが既定で読み込まれません。環境変数で戻す前に確かめるべき3つの分岐と、チェックリストの代わりに置いた作業台帳の実装、並列実行で台帳が壊れた境界の実測結果をまとめます。
Claude Code2026-08-04
ファイルシステムの分離をネットワークと別に締める — 触ったパスを採り、書き込み面から絞る
Claude Code v2.1.216 でファイルシステムの分離をネットワークと切り離して指定できるようになりました。締める前にジョブが触ったパスを strace で採取し、読み取り面と書き込み面を分けて測った実務メモ。同じジョブを3回走らせたときのパス集合の安定率と、接頭辞に畳んだときの安定率の差まで実測しています。
Claude Code2026-09-05
拒否ルールが見ていたのはファイル名で、そこへ届く経路ではありませんでした
deny に書いた 1 行が、同じファイルへ届く別の書き方を素通りさせていました。経路を機械的に並べて、版を上げた日に効きを確かめる小さな検査表の作り方をお伝えします。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます