CLAUDE LABJP
COGNI — Cognizant and Anthropic expanded their partnership on July 27 to bring Claude to enterprise clients through a wider delivery footprintTUNNEL — MCP connectors gained embedded UI, enterprise-managed auth, observability, and private network tunnels, so internal services can be reached without exposing them publiclyOSS — Anthropic said it does not support banning open-source AI, while calling for targeted legal and commercial frameworks to prevent illicit model distillation (July 28)PACING — More than 1,100 employees across OpenAI, Anthropic, Google, and Meta signed a letter asking the US government to build tools for a verifiable international pacing mechanismOPUS5 — Opus 5 reportedly doubles Opus 4.8 on Frontier-Bench v0.1 and surpasses Fable 5 on OSWorld 2.0 at roughly one-third the costFABLE5 — Fable 5 plan access was finalized on July 20: Max and Team Premium include it up to 50% of weekly limits, while Pro and Team Standard move to metered creditsCOGNI — Cognizant and Anthropic expanded their partnership on July 27 to bring Claude to enterprise clients through a wider delivery footprintTUNNEL — MCP connectors gained embedded UI, enterprise-managed auth, observability, and private network tunnels, so internal services can be reached without exposing them publiclyOSS — Anthropic said it does not support banning open-source AI, while calling for targeted legal and commercial frameworks to prevent illicit model distillation (July 28)PACING — More than 1,100 employees across OpenAI, Anthropic, Google, and Meta signed a letter asking the US government to build tools for a verifiable international pacing mechanismOPUS5 — Opus 5 reportedly doubles Opus 4.8 on Frontier-Bench v0.1 and surpasses Fable 5 on OSWorld 2.0 at roughly one-third the costFABLE5 — Fable 5 plan access was finalized on July 20: Max and Team Premium include it up to 50% of weekly limits, while Pro and Team Standard move to metered credits
Articles/Claude Code
Claude Code/2026-07-30Advanced

You Added a Second Working Root — Now Ask How Far Your Ignore Rules and Deny Patterns Actually Reach

Adding a working root mid-session does not carry your ignore rules or deny patterns with it. Measured evidence from git check-ignore and a three-way matcher comparison, plus the scope-delta audit script I now run from the DirectoryAdded hook.

Claude Code206hooks15MonorepoSecurity11Operations13

Premium Article

I ran /add-dir mid-session to pull in a second repository, because I wanted the iOS build configuration side by side with what I was already editing.

Working as an indie developer with an app repository on one side and a site repository on the other, this kind of crossing comes up constantly.

The editing part went fine. What bothered me came after.

I ran my usual sweep for files that might carry credentials, and the count had not moved. The root I had just added contains a pile of configuration files. The number should have changed.

A few minutes of digging gave a boring answer. The sweep was still anchored to the first root. Adding a root widens what you can edit; it does not carry the assumptions hanging off the old root along with it.

Session healthy. No errors. Just a safety net stretched across the wrong ground.

An Added Root Is a Sibling, Not a Child

When you add a root with /add-dir or the SDK's register_repo_root, it feels like the working area expanded.

What actually exists is a second independent root sitting alongside the first. Siblings, not parent and child.

The distinction bites hardest on ignore rules. Git exclusion rules stop at the repository boundary. That is unremarkable as a specification, and it becomes an operational hole the moment you have two roots.

I rebuilt the situation locally to check. root_a gets a .gitignore that excludes secrets/; root_b gets none. Both contain the same secrets/prod.env.

# root_a: the rule applies
$ git -C root_a check-ignore -v secrets/prod.env
.gitignore:1:secrets/	secrets/prod.env
 
# asking root_a about a file in root_b is simply refused
$ 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
 
# in root_b, the same path is not excluded at all
$ git -C root_b check-ignore -v secrets/prod.env
(no output, exit code 1)

git status makes the gap visible.

$ git -C root_a status --porcelain
A  .gitignore
A  src/app.ts
 
$ git -C root_b status --porcelain
A  secrets/prod.env staged in the added root
A  src/app.ts

In root_a the file never appears. In root_b it walks straight through to staged.

The practical rule: any judgement about an added root has to run git inside that root. Code that peeks in from the parent with a relative path returns a quietly wrong answer, and the answer it returns is "safe".

The Same Deny Pattern Means Different Things to Different Matchers

Ignore rules were the first surprise. permissions.deny was the second.

Deny is a separate system — string pattern matching rather than git semantics. And here an assumption broke: the same pattern gives different results depending on which matcher reads it.

I lined up three that people actually reach for — fnmatch, Path.match, and glob's ** semantics — and pointed them at the same paths.

import fnmatch, re
from pathlib import PurePosixPath
 
def m_glob(p, pat):
    """Reproduce glob's ** semantics: zero or more directories."""
    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))

Here is what came back.

PatternPathfnmatchPath.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

Two rows deserve attention.

The *.env rows: under fnmatch and Path.match the pattern matches config/prod.env. The * crosses the slash. If you wrote it with glob in mind, thinking "top level only", it reaches further than intended.

The **/config/*.env rows: glob matches, the other two do not. **/ is read as "at least one directory segment" rather than "zero or more", so a config/ sitting directly under the root falls out.

That second one runs opposite to what I expected. I had been adding **/ prefixes on the assumption that they only ever widen coverage. Depending on the matcher they narrow it — and the thing they drop is the top level of the added root, which is exactly where configuration files cluster.

With a single root you never meet this. There is only one directory tree, shallow paths are rare, and every spelling of the pattern agrees. Add a second, shallower tree and the disagreement surfaces.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Measured proof that ignore rules do not compose across roots — the same secrets/ directory is skipped in one root and staged in the other
A matcher comparison table showing the same deny pattern diverging — `*.env` crosses slashes, and `**/config/*.env` misses files directly under the added root
The ~70-line scope-delta audit run from DirectoryAdded, with its real output: 2 uncovered secret candidates, 1 case mismatch, 2 path collisions
Timed at 21.8 ms on a 2,882-file added root — and why the delta audit is not actually faster than a full rescan (under 1% apart)
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Claude Code2026-07-29
The MCP Server Connects Fine and Still Hands Back Zero Tools — Catching Silent Capability Drift with a Tool Manifest Diff
A missing credential does not break the MCP handshake. initialize succeeds, tools/list succeeds, and the array comes back empty. Here is the measured behaviour, and a preflight that locks the expected tool surface and fails closed before the agent ever starts.
Claude Code2026-07-25
Locking down Claude Code sandbox egress with strictAllowlist
Tightening automation egress with strictAllowlist in Claude Code v2.1.219, plus measured failure timings that tell a policy deny from DNS and real outages.
Claude Code2026-07-18
I Believed Plan Mode Only Read — Replacing That Belief With Machinery
Claude Code 2.1.212 fixed a bug where plan mode ran file-modifying Bash commands without the permission prompt or the SDK canUseTool callback. Here is what could happen while that assumption was broken, how to verify your own setup, and how to stop leaning on a mode name for safety.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →