CLAUDE LABJP
MCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attributionMCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attribution
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 Code225hooks16MonorepoSecurity11Operations16

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-08-19
Fixing the Code Doesn't Evict a Broken Page From the Edge Cache
I shipped a fix and the broken page kept serving. The problem was not the code but the edge cache, which had stored a broken HTML response because the store decision looked only at the status code. Here is the integrity guard I now run before every cache write, and the thresholds I had to tune in production.
Claude Code2026-08-16
Tracking Multi-Step Work in Claude Code Now That the Todo Tools Are Off by Default
Since Claude Code v2.1.233, TodoWrite and the Task tools are left out on the newer models. Here are the three branches to check before you set the opt-in variable, the PostToolUse ledger I put in their place, and the measured point where that ledger quietly corrupts under parallel agents.
Claude Code2026-08-03
Existence Checks Pass, Writes Fail — Probing Capabilities Before an Unattended Run
A directory existing and a directory being writable are two different facts. Measured results from five broken-environment cases, and a capability-probe preflight for unattended Claude Code runs.
📚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 →