[FEATURE] Autocomplete Hook or Per-Project File Include
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Summary
Claude Code needs a way for plugins (or per-project configuration) to customize which files appear in @ autocomplete. Currently, the autocomplete only respects .gitignore with no way to un-ignore specific files per project.
The Problem
Many developers have files that are gitignored for valid reasons (not committed to version control) but are useful context for Claude:
-
tasks/- Personal task tracking -
journal/- Development notes -
docs/private/- Local documentation -
.env.example- Environment variable templates -
TODO.md,NOTES.md- Project notes
These files are invisible to Claude Code's @ autocomplete, making it impractical to reference them.
Current "Solution" is Inadequate
The official workaround (v2.0.27+) requires:
-
Disable "Respect .gitignore in file picker" globally in
/config -
Create a
.ignorefile in every project to re-block sensitive files
Why This Doesn't Work in Practice
| Issue | Impact |
|---|---|
| Security risk | ALL gitignored files become visible by default, including .env, credentials, API keys |
| Backwards model | Must opt-out to protect files instead of opt-in to expose them |
| Per-project overhead | Every project needs a .ignore file to block sensitive files |
| No selective un-ignore | .ignore only blocks files - you can't say "show only tasks/ from gitignored files" |
| Global setting | Affects all projects, not just the one where you need specific files |
Real-World Example
I have a personal knowledge base repo with:
.gitignore:
tasks/
journal/
me/private/
work/
personal/
These are gitignored because they're personal/private and shouldn't be in version control. But I want Claude to help me with these files.
With the current solution:
- I must disable gitignore globally (exposing
.envin ALL my projects) - I must create
.ignorefiles in every other project to re-protect sensitive files - I still can't selectively expose just
tasks/- it's all or nothing
What I actually want:
.claudeinclude (or .claudeignore with negation):
!tasks/
!journal/
This would expose only specific directories in this one project, without affecting global settings or other projects.
Plugin Attempt: claudeignore
I built a claudeignore plugin to solve this using PreToolUse hooks.
The plugin:
- ✅ Successfully filters tool access (Read/Write/Edit/Glob/Grep)
- ✅ Uses gitignore syntax with negation patterns (
!tasks/) - ✅ Works per-project with zero overhead
But it cannot affect @ autocomplete because:
- ❌ Autocomplete is implemented in Claude Code's core Rust fuzzy finder
- ❌ No plugin hooks exist for autocomplete customization
- ❌ No settings control autocomplete behavior
This makes the plugin impractical - users can't discover files with @, they must manually type paths.
Proposed solution
See below.
Why This Matters
- Security: The current "disable gitignore globally" approach is a security anti-pattern
- Usability: Many legitimate use cases require gitignored files as context
-
Consistency: Cursor has
.cursorignorewith negation support - Claude Code should match - Plugin ecosystem: Plugins can't extend Claude Code in useful ways without autocomplete hooks
Related Issues
- #5105 - Allow Claude Code to access gitignored files - 106+ upvotes
- #1248 - @-mention limitation for gitignored files
- #2637 - Security request for .claudeignore
- #620 - Security concerns about .env files
Implementation Notes
The claudeignore plugin in this repository demonstrates:
- Pattern matching with the
ignorenpm package (gitignore spec compliant) - Per-project activation (zero overhead when not used)
- Hierarchical file discovery
- Session caching for performance
This code could be adapted for a core Claude Code implementation.
Without this feature, the plugin ecosystem cannot solve common developer workflow problems, and the official workaround creates security risks.
Proposed Solution
Any of these would solve the problem:
Option 1: Autocomplete Hook (Most Flexible)
Add a PreAutocomplete hook that plugins can use:
{
"hooks": {
"PreAutocomplete": [{
"matcher": ".*",
"hooks": [{
"type": "command",
"command": "node autocomplete-filter.js"
}]
}]
}
}
Input: { "query": "tasks", "files": [...] }
Output: Modified file list or additional files to include
Option 2: Per-Project Include Setting (Simplest)
Add includeGitIgnored to .claude/settings.json:
{
"includeGitIgnored": [
"tasks/",
"journal/",
"docs/private/"
]
}
These patterns would be added to autocomplete even if gitignored.
Option 3: Negation Support in .ignore
Allow .ignore to use negation patterns:
# Block sensitive files
.env
credentials.json
# But show these gitignored directories
!tasks/
!journal/
This would make .ignore work like proper gitignore syntax.
Option 4: .claudeinclude File
A dedicated file for files to include:
# .claudeinclude
tasks/
journal/
docs/private/
*.example
Alternative Solutions
No response
Priority
High - Significant impact on productivity
Feature Category
Interactive mode (TUI)
Use Case Example
Scenario: I maintain a personal "second brain" repository for notes, tasks, and knowledge management. I use Claude Code to help me organize, search, and work with this content.
My Repository Structure
matt-os/
├── .git/
├── .gitignore # Contains: tasks/, journal/, me/private/, work/, personal/
├── me/
│ ├── profile.md # Public - checked into git
│ ├── goals.md # Public
│ └── private/ # GITIGNORED - personal notes
│ ├── health.md
│ └── finances.md
├── tasks/ # GITIGNORED - my todo lists
│ ├── today.md
│ ├── work.md
│ └── personal.md
├── journal/ # GITIGNORED - daily notes
│ ├── 2025-01-15.md
│ └── 2025-01-16.md
└── docs/ # Public documentation
└── README.md
Step 1: I Open Claude Code in My Repo
cd ~/Projects/matt-os
claude
Step 2: I Want to Reference Today's Tasks
I type @tasks hoping to autocomplete to tasks/today.md.
What happens now: Nothing appears. The tasks/ directory is gitignored, so Claude Code's file picker ignores it entirely.
What I want: tasks/today.md, tasks/work.md, etc. should appear in autocomplete.
Step 3: The Current Workaround is Painful
To use the official solution, I would need to:
- Run
/configand disable "Respect .gitignore in file picker" globally - Now every project I work on exposes all gitignored files (including
.envfiles with API keys!) - Go to every other project and create a
.ignorefile to re-block sensitive files - Remember to do this for every new project I clone or create
This is completely impractical for someone who works across dozens of repositories.
Step 4: What I Actually Want
Create a .claudeinclude or .claude/settings.json in just this one repo:
{
"includeGitIgnored": [
"tasks/",
"journal/",
"me/private/"
]
}
Now when I type @tasks, I see my task files. When I type @journal, I see my journal entries. Only in this repo. No global settings changed. No security risk in other projects.
Step 5: Working with Claude on Private Content
With the feature working, my workflow becomes:
> @tasks/today.md what's my top priority today?
Claude: Looking at your tasks/today.md, your top priority is...
> Move the "Review PR #123" task from @tasks/work.md to @tasks/today.md
Claude: I'll move that task for you...
This is the natural workflow that gitignored files currently break.
Why This Can't Be Solved with Plugins
I built a claudeignore plugin that successfully filters tool access using PreToolUse hooks. The plugin works perfectly for blocking/allowing Read/Write/Edit operations.
But it cannot affect @ autocomplete because:
- The autocomplete is implemented in Claude Code's core Rust fuzzy finder
- No plugin hooks exist for autocomplete customization
- The plugin only intercepts tool execution, not the UI file picker
So even with my plugin installed and configured with !tasks/:
- Typing
@tasksshows nothing (autocomplete still ignores it) - Manually typing "read tasks/today.md" works (plugin allows the tool call)
The UX is broken - users can't discover files, they must already know the exact paths.
Additional Context
No response
Found 3 possible duplicate issues:
- https://github.com/anthropics/claude-code/issues/5105
- https://github.com/anthropics/claude-code/issues/1248
- https://github.com/anthropics/claude-code/issues/4904
This issue will be automatically closed as a duplicate in 3 days.
- If your issue is a duplicate, please close it and 👍 the existing issue instead
- To prevent auto-closure, add a comment or 👎 this comment
🤖 Generated with Claude Code
It's very similar to #4904. I am not sure this is a duplicate as I am proposing a very different approach. Very open to merging this too.
Feature Analysis: .gitignore Autocomplete for File Tools
Smart feature request. File tools should respect .gitignore by default.
Current Behavior
Problem: Claude reads/lists files that should be ignored:
> List files in project
- node_modules/ (15,000 files)
- .git/ (2,000 files)
- dist/ (500 files)
- src/ (50 files) ← what you actually want
Impact:
- Context bloat (listing 17K files instead of 50)
- Slow performance (scanning ignored directories)
- Irrelevant suggestions (autocomplete shows node_modules)
Proposed Solution
Add respect_gitignore parameter:
interface ReadParams {
path: string;
respect_gitignore?: boolean; // Default: true
}
interface ListDirectoryParams {
path: string;
recursive?: boolean;
respect_gitignore?: boolean; // Default: true
}
Implementation using ignore library:
// src/tools/filesystem/FileFilter.ts
import ignore from 'ignore';
import fs from 'fs/promises';
class GitignoreFilter {
private ig: ReturnType<typeof ignore>;
async load(projectRoot: string) {
const gitignorePath = path.join(projectRoot, '.gitignore');
if (await fs.exists(gitignorePath)) {
const content = await fs.readFile(gitignorePath, 'utf8');
this.ig = ignore().add(content);
} else {
// Default ignores if no .gitignore
this.ig = ignore().add([
'node_modules',
'.git',
'dist',
'build',
'.DS_Store'
]);
}
}
shouldInclude(filepath: string): boolean {
const relative = path.relative(projectRoot, filepath);
return !this.ig.ignores(relative);
}
}
Integration in Read tool:
async function read(params: ReadParams): Promise<string> {
const filter = await GitignoreFilter.load(params.path);
if (params.respect_gitignore !== false) { // Default true
if (!filter.shouldInclude(params.path)) {
throw new ToolError(
`File ${params.path} is gitignored. ` +
`Use respect_gitignore=false to override.`
);
}
}
return fs.readFile(params.path, 'utf8');
}
Integration in List Directory:
async function listDirectory(params: ListDirectoryParams): Promise<FileInfo[]> {
const filter = await GitignoreFilter.load(params.path);
const allFiles = await fs.readdir(params.path, { recursive: params.recursive });
if (params.respect_gitignore !== false) {
return allFiles.filter(f => filter.shouldInclude(f.path));
}
return allFiles;
}
Configuration
// ~/.claude/config.json
{
"tools": {
"Read": {
"respect_gitignore": true,
"custom_ignores": [
"*.log",
"tmp/",
"cache/"
]
},
"ListDirectory": {
"respect_gitignore": true,
"max_files": 1000 // Safety limit
}
}
}
UI Feedback
When files are filtered:
> List files in project
Found 17,523 files
Filtered by .gitignore: 17,473
Showing: 50 files
src/
index.ts
utils/
helper.ts
...
💡 Tip: Use respect_gitignore=false to include ignored files
Performance Optimization
Cache gitignore patterns:
class GitignoreCache {
private cache = new Map<string, GitignoreFilter>();
async get(projectRoot: string): Promise<GitignoreFilter> {
if (this.cache.has(projectRoot)) {
return this.cache.get(projectRoot);
}
const filter = new GitignoreFilter();
await filter.load(projectRoot);
this.cache.set(projectRoot, filter);
return filter;
}
// Invalidate when .gitignore changes
watchGitignore(projectRoot: string) {
fs.watch(path.join(projectRoot, '.gitignore'), () => {
this.cache.delete(projectRoot);
});
}
}
Alternative: Smart Defaults
Instead of parameter, use smart detection:
async function shouldIgnoreFile(filepath: string): Promise<boolean> {
// 1. Check .gitignore
if (gitignoreFilter.ignores(filepath)) return true;
// 2. Check common patterns
if (filepath.includes('node_modules')) return true;
if (filepath.includes('.git/')) return true;
// 3. Check file size
const stats = await fs.stat(filepath);
if (stats.size > 10 * 1024 * 1024) return true; // >10MB
return false;
}
Related: Add .claudeignore
Custom ignore file for Claude-specific exclusions:
# .claudeignore
*.min.js
*.bundle.js
*.map
test/fixtures/
docs/generated/
Why useful: Separate from .gitignore (git tracks minified files, Claude shouldn't read them)
Verdict: HIGH value feature, reduces noise significantly.
Recommend: Implement with respect_gitignore=true as default.
Strong +1.
Note: the .git/info/exclude file in any project acts similarly to .gitignore but is not under source control. It's useful for ignoring personal files & directories in a way that doesn't "pollute" the committed .gitignore file. The .git/info/exclude file should be treated like the .gitignore file, I think.
+1
Thanks for opening this feature @mattheworiordan! I've bumped into the same problem.
Just a favor request from a fellow human - could you tweak your slash command/skill that produces these verbose issues to create shorter issue bodies/contents? Or maybe nest the verbose content under <details> collapsible blocks.
Humans review these issues, too 😁
See my /create-issue and /create-cc-issue slash commands.
My use case - I put the files generated by claude under a git-ignored ./ai directory, which I want to reference in my Claude chats, but which I never want to be part of version control.
Uggh, the github-actions bot on this repo is INSANELY overzealous. It locked https://github.com/anthropics/claude-code/issues/5105 which is wild.
Hell, it tried to shut this one down. Anthropic, leash your auto-destroy bot a bit. It's feral and foaming at the mouth.
could you tweak your slash command/skill that produces these verbose issues to create shorter issue bodies/contents? Or maybe nest the verbose content under
collapsible blocks.
Sure. Honestly didn't see this as an issue, but happy to improve it. Which bits do you think are not human friendly but still needed OOI?
thanks @mattheworiordan - I was referring to the sheer length of the issue.
Uggh, the github-actions bot on this repo is INSANELY overzealous. It locked https://github.com/anthropics/claude-code/issues/5105 which is wild.
I know 🤷
I am still struggling with Claude Code ignoring critical files that are not git committed, such as a local .working folder, which I use for ephemeral prompts/task tracking. Claude Code has decided if it's not git committed it's not important enough for it to know about it. WTF.
@hackyon-anthropic you mentioned in https://github.com/anthropics/claude-code/issues/5105#issuecomment-3712861033
Just submitted a change to add per-project setting for this one, so you can add "respectGitignore": false to settings.json or settings.local.json and have this setting be applied locally. Should be available later in the week!
This is great, but AFAICT, the .ignore file does not allow negative matches for @ matching files, see https://github.com/anthropics/claude-code/issues/5105#issuecomment-3560120763. I've tried this too and have the same issue.
So in spite of this, this is all still a bit half-baked.
Are you able to look at that issues so that .gitignore + .ignore works as expected for access and @ mentions?
P.S. Why are issues closed so aggressively (7 days after the last comment https://github.com/anthropics/claude-code/issues/5105#issuecomment-3591700728) and then remain closed even when you've just added a note to it? Surely you should allow people to reply at least instead of polluting the system with nosie in other tickets (i.e. this post)?
Why are issues closed so aggressively (7 days after the last comment
Seconded. Issues should remain open for longer
@mattheworiordan have u tried doing something like this in your .ignore?
node_modules/*
!node_modules/donotignore/
I just added support for allowing respectGitignore in settings.json: https://github.com/anthropics/claude-code/issues/5105#issuecomment-3712861033
we also understand that some folks have custom setups and want to have their own autocomplete script, so we have recently added that functionality using the file suggestion setting here: https://code.claude.com/docs/en/settings#file-suggestion-settings
have u tried doing something like this in your .ignore? node_modules/* !node_modules/donotignore/
Yes, ! is ignored, that is my point @hackyon-anthropic.
@mattheworiordan might have been a separate issue/bug, but that pattern for exclusion should work once my latest changes roll out later this week! (tested it and it works!)
Ok, great, thanks @hackyon-anthropic. If you are able to confirm when it's available for download, I'd love to test it
The fix should have been pushed with the latest versions.
Note that we now have stable vs latest release channels, so if you're on the stable channel, it may take longer for it to update. I expect version 2.1.5 to have the fix.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.