The first wall most people hit with Gemini CLI or Code Assist is irrelevant files leaking into the context and making answers vague. I ran into this myself the day I added Code Assist to a monorepo at work — within minutes it was happily reading every type definition in node_modules and burning tokens for nothing useful.
The official answer to this is .geminiignore. It uses the same syntax as .gitignore, but the purpose is different: not "don't commit," but "don't let Gemini see this." This guide walks through how to use it well, with the rough edges I hit in real projects.
What .geminiignore Actually Does
.geminiignore is a plain text file you place at the root of your repository. Both Gemini CLI and Code Assist consult it when assembling the context they send to the model, and they skip anything that matches. The syntax is .gitignore-compatible: line-based glob patterns, ! for un-ignoring, # for comments.
It is easy to confuse with .gitignore, but they answer different questions.
.gitignore— should this be committed?.geminiignore— should the AI read this?
A built dist/ folder usually wants to live in .gitignore but not be visible to Code Assist. Test snapshots are the opposite: often excluded from Git but valuable as context. The two files solve different problems, and trying to merge them eventually breaks somewhere.
One thing the official docs do not call out: in my testing, .geminiignore works most reliably when there is exactly one file, sitting at the project root. Files placed in subdirectories were either ignored outright or behaved inconsistently across tools.
Why Cleaning Up Context Improves Answer Quality
Gemini 3 models support 1M–2M token context windows, but answer quality consistently degrades as input grows. This is the well-known "Lost in the Middle" effect — the model attends less to information buried in the middle of a long input.
Three reasons to take .geminiignore seriously:
- Better answers. Removing noise lets the model focus on the code that actually matters.
- Lower cost. Fewer tokens read means less spend on metered APIs and more headroom inside monthly quotas.
- Faster responses. Shorter inputs are noticeably faster to process, especially with the Flash family.
In my own project, Code Assist's Agent Mode was loading package-lock.json on every request, eating roughly 300K tokens by itself. Adding it to .geminiignore dropped the same task to about a tenth of the tokens, and that month's bill came in visibly lower.
A Solid Baseline .geminiignore
Here is a starter file I have settled on after using it across several projects. It tends not to break anything regardless of stack.
# .geminiignore — Generic baseline
# Dependencies
node_modules/
.pnpm-store/
vendor/
.venv/
__pycache__/
# Build artifacts
dist/
build/
.next/
out/
target/
*.min.js
*.bundle.js
# Lock files (huge, no value as context)
package-lock.json
pnpm-lock.yaml
yarn.lock
poetry.lock
Cargo.lock
# IDE / OS
.idea/
.vscode/settings.json
.DS_Store
# Secrets (most important)
.env
.env.*
!.env.example
*.pem
*.key
secrets/
# Large assets
*.mp4
*.zip
*.tar.gz
public/uploads/Treat the secrets section as the highest priority. If .env makes its way into Gemini's context, your API keys can show up in generated code or answers. Even if .gitignore already covers them, listing them again in .geminiignore is the right kind of belt-and-suspenders.
Monorepo Patterns
In a recent monorepo I worked on, having both apps/web and apps/admin open meant noise from the other app constantly bled into Code Assist's answers. A .geminiignore like the one below lets you switch focus quickly:
# .geminiignore — Monorepo
# Shared exclusions
node_modules/
dist/
.turbo/
.next/
# Toggle: comment out to focus on admin only
# apps/web/
# packages/ui tests are gold for context — un-ignore them
packages/ui/dist/
!packages/ui/src/__tests__/The trick here is leaning on ! to selectively bring things back. You can ignore packages/ui/dist/ while still letting __tests__/ through, because tests are some of the best material the model has for learning how an API is meant to be called. I almost never exclude test directories.
Switching .geminiignore per branch is also worth knowing about. On feature/admin-refactor, ignore apps/web/. On feature/web-redesign, do the opposite. The model stays anchored to whatever you are actually working on.
Measuring the Impact
It's easy to talk yourself into believing .geminiignore is helping. Numbers are better. The Gemini API exposes countTokens, which returns the exact token count a payload would use. A small script makes the comparison concrete.
# context_token_check.py
# Compare token usage before and after editing .geminiignore
import os
import pathlib
import fnmatch
from google import genai
# Load patterns from .geminiignore
def load_ignore_patterns(root: pathlib.Path) -> list[str]:
ignore_file = root / ".geminiignore"
if not ignore_file.exists():
return []
return [
line.strip()
for line in ignore_file.read_text().splitlines()
if line.strip() and not line.startswith("#")
]
def is_ignored(path: pathlib.Path, patterns: list[str]) -> bool:
rel = str(path)
for pat in patterns:
if fnmatch.fnmatch(rel, pat) or rel.startswith(pat.rstrip("/")):
return True
return False
def collect_context(root: pathlib.Path, patterns: list[str]) -> str:
chunks = []
for path in root.rglob("*"):
if path.is_file() and not is_ignored(path.relative_to(root), patterns):
try:
chunks.append(path.read_text(errors="ignore"))
except Exception:
continue
return "\n".join(chunks)
if __name__ == "__main__":
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
root = pathlib.Path(".")
patterns = load_ignore_patterns(root)
context = collect_context(root, patterns)
# countTokens returns the actual billable token count
result = client.models.count_tokens(
model="gemini-2.5-flash",
contents=context,
)
print(f"Total tokens: {result.total_tokens:,}")
print(f"Patterns active: {len(patterns)}")Expected output looks like:
Total tokens: 38,420
Patterns active: 14
Run it before and after editing .geminiignore and you can see which patterns are actually pulling their weight. In one project, adding a single line for package-lock.json took the count from 300K tokens down to 40K.
Common Mistakes I've Made
A few things have tripped me up enough times that they are worth flagging.
The first is file location. .geminiignore belongs at the project root, next to .git. Putting it in a subdirectory is sometimes silently ignored — there is no warning when the file is in the wrong place, so this is the first thing to check when patterns "aren't working."
The second is editor reload. Code Assist's VS Code extension reads .geminiignore on startup, so changes only take effect after a restart. Gemini CLI picks up changes on its next launch.
The third is ! ordering. Like .gitignore, later patterns win, so un-ignoring (!pattern) must come after the ignore line, not before it. Reverse the order and your un-ignore quietly does nothing.
How It Fits with Other Gemini Config Files
The Gemini ecosystem has a handful of similarly-named files, and it helps to keep them straight.
.geminiignore— what to exclude from contextgemini.md/GEMINI.md— instructions, conventions, project background to pass to the model.gemini/config.yaml— Gemini CLI behavior (model, output format, etc.)
These work independently. .geminiignore controls exclusion, gemini.md provides direction, and config.yaml shapes behavior. We covered the instruction file in detail in the GEMINI.md setup guide — pairing them roughly doubles the value of either.
If the CLI side itself still feels new, the Gemini CLI complete guide and the Code Assist Agent Mode walkthrough make a good companion read. .geminiignore clicks more once the surrounding tooling is familiar.
Start with One Line
If you take one thing from this guide, it's this: add a single line to .geminiignore today and see how it feels. Just node_modules/ and package-lock.json are usually enough to noticeably shift Code Assist's responsiveness and answer quality.
A safe order to grow the file: secrets first (.env*), then large files (lock files, build output), then noisy dependency directories (node_modules/, vendor/). You don't need a perfect file from day one — start small, watch your token counts, and iterate.