You ask Claude Code to look at .env.example and it tells you the file doesn't exist. You ask it to inspect the bundled output in dist/index.js and you get the same answer. Both files are sitting right there on disk. The first time this happened to me, I burned a few minutes retyping the path before I realized what was going on.
This isn't a bug — it's a deliberate default. Claude Code's Glob and Grep tools respect your .gitignore out of the box. Once you understand why, the workaround takes about ten seconds and saves you from a lot of confused back-and-forth.
Why .gitignore Hides Files From Search
Internally, Claude Code's Glob tool behaves like fd and the Grep tool behaves like ripgrep. Both of those tools share the same friendly default: anything Git ignores stays out of the results.
For everyday work, that's exactly what you want. Nobody enjoys grepping through node_modules/ and pulling back ten thousand hits. The whole reason Claude Code stays responsive on large repos is that it skips this noise by default. The same goes for .next/, target/, __pycache__/, and the dozens of other build directories that pile up in modern projects. Filtering them out of search results is what makes "search the codebase" actually feel like searching the codebase rather than searching the filesystem.
But every once in a while the helpfulness backfires. Specifically, when:
- You want to look at
.env.example(and your.gitignorehas.env*) - You need to inspect the contents of
dist/,build/, or.next/ - You want to read coverage reports under
coverage/or test snapshots - You added a folder to
.gitignorefor personal tinkering and now Claude can't see it
In other words, "I don't want this in Git, but I want Claude to see it for this session" turns out to be a more common situation than you'd expect.
Three Patterns Where This Trips People Up
In practice, the same three scenes account for most of the confusion.
Scene 1: "That file doesn't exist"
You: Read me .env.example
Claude Code: [runs Glob] No file matched at that path.
The file is right there at the project root, but .env* is in .gitignore, so Glob refuses to acknowledge it. The frustrating part is that Claude isn't lying — from its tool's perspective, the file genuinely is invisible.
Scene 2: Reviewing a build artifact
You want to talk about what ended up in dist/index.js after bundling. Claude Code reports that dist/ doesn't exist. Same root cause. This one bites hardest when you're debugging "why is my production bundle so large" and Claude can only see the source files, not the output you actually care about.
Scene 3: Sharing local logs or screenshots
You captured something into screenshots/ or .cache/ for debugging. Claude can't see them, so it gives you generic advice instead of the targeted suggestion you wanted. If you're using a tmp/ or local/ folder as a personal scratchpad and it's gitignored, the same thing applies.
Fix #1: Use the Read Tool With an Explicit Path
There are three ways through this, but the one I reach for first is the simplest: stop asking Claude Code to find the file, and tell it where to look. The Read tool — unlike Glob and Grep — doesn't consult .gitignore at all. If you give it a path, it reads the file.
The phrasing in chat is just:
Read .env.example with the Read tool. Path: ./project-root/.env.example
If you'd rather skip the lookup entirely, paste the contents in yourself:
# Print just the part you want to discuss
cat .env.example | head -50The trick is to switch from "search and find" to "I already know where it lives." That sidesteps the search-tool layer where .gitignore is enforced. It also tends to produce cleaner conversations, because Claude isn't spending tool calls hunting around — it goes straight to the content.
When you don't actually know the path, find it on the shell first and then hand the result to Claude:
# Plain find ignores .gitignore semantics
find . -name ".env.example" -not -path "*/node_modules/*"
# Or list everything Git is currently ignoring
git ls-files --others --exclude-standardThe second command is particularly handy — it prints every file that exists on disk but isn't tracked by Git, which is exactly the set of files that Glob is hiding from you.
A small habit that's saved me time: when I'm unsure whether a path is being ignored, I run git status --ignored first. It splits its output into three buckets — staged, unstaged, and ignored — and the ignored section makes it obvious which files Claude Code's search tools are deliberately skipping. Once you have that list, the decision tree is trivial: if I want the file, I read it directly by path; if I want a class of files, I switch to Bash with --no-ignore.
Fix #2: Run Search Through Bash With --no-ignore
If you want Claude Code itself to do the searching, the cleanest approach is to invoke ripgrep or fd from the Bash tool with --no-ignore:
# Search across all files, ignoring .gitignore (ripgrep)
rg --no-ignore "DATABASE_URL" .
# Include hidden files and dot-folders too
rg --no-ignore --hidden "API_KEY" .
# Filename search (fd)
fd --no-ignore --hidden "\.env" .By default, both tools honor .gitignore. The --no-ignore flag turns off that behavior, and --hidden brings dot-folders like .next/ into scope.
One caveat: full-text searching across node_modules/ is brutally slow on any non-trivial project. I usually narrow the scope explicitly:
# "Show me dist and coverage, but skip node_modules"
rg --no-ignore --hidden \
--glob '!node_modules/**' \
--glob '!.git/**' \
"TODO" .Hand-rolled --glob filters end up faster than letting the tool guess. Whenever Claude Code feels sluggish on a search, an overly broad scope is almost always the reason. A more aggressive variation I've come to like:
# Only look in build outputs, nothing else
rg --no-ignore --hidden \
--glob 'dist/**' \
--glob 'build/**' \
--glob '.next/**' \
"process.env" .For more on Bash-tool quirks, see the Claude Code Bash tool error guide.
Fix #3: Patch the .gitignore Itself (Last Resort)
If you really want the standard Glob/Grep tools to recognize a file, you can edit .gitignore. I'd put this last on the list, since changes to that file affect the entire repo and risk committing things you didn't mean to.
If you go this route, the safest option is to use .git/info/exclude for repo-local ignores (it's not committed and only affects your machine) or to selectively un-ignore the specific file you want exposed:
# .gitignore
.env*
# Make .env.example visible again
!.env.example
With this in place, .env.example shows up in Glob/Grep like any other file, and the rest of .env* keeps being ignored. If you only need the change for a debugging session, edit and don't commit. The ! syntax is also useful for whitelisting package-lock.json or pnpm-lock.yaml patterns when you're inside a parent folder that's broadly ignored.
When .gitignore behavior surprises you, git check-ignore -v <path> tells you in one line which rule from which file is hiding the path. It's a small command but it removes most of the guesswork. Worth wiring into your shell aliases if you find yourself diagnosing this often.
What I Actually Use Day to Day
Remembering all of this for every task is unrealistic, so I've settled into a small playbook. Borrow whatever feels useful.
- Path is known: hand it to the
Readtool directly. Covers about 90% of cases - Need to search but can scope tight:
rg --no-ignore --hidden --glob '!node_modules/**'from Bash - A file you'll keep referring to (like
.env.example): add a!exception to.gitignoreand commit it - Throwaway debug files: park them somewhere that isn't ignored to begin with — for example, don't add
tmp/to.gitignoreif you want Claude to read it
One more thing worth adding to the playbook: if you maintain a personal CLAUDE.md for your project, jot down which folders are gitignored on purpose and how you'd like Claude to treat them. A single line like "treat dist/ as read-only — only inspect, never edit" goes a surprisingly long way. Claude Code reads that context on session start and stops asking permission to do things you've already decided about.
For broader configuration questions, the Claude Code settings.json complete guide walks through permissions, env, and the rest of the surface area. If "Claude can't see my environment variables" sounds familiar, the ANTHROPIC_API_KEY troubleshooting guide covers a closely related class of problems.
The next time Claude Code says a file doesn't exist, suspect .gitignore first. To make this stick, run git check-ignore -v .env.example in your own repo today and watch which rule fires. Once the mental model clicks, the tool's behavior stops feeling mysterious — and the back-and-forth that used to happen on every "that file doesn't exist" reply just goes away.