claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[FEATURE] Autocomplete Hook or Per-Project File Include

Open mattheworiordan opened this issue 3 months ago • 13 comments

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:

  1. Disable "Respect .gitignore in file picker" globally in /config
  2. Create a .ignore file 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:

  1. I must disable gitignore globally (exposing .env in ALL my projects)
  2. I must create .ignore files in every other project to re-protect sensitive files
  3. 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

  1. Security: The current "disable gitignore globally" approach is a security anti-pattern
  2. Usability: Many legitimate use cases require gitignored files as context
  3. Consistency: Cursor has .cursorignore with negation support - Claude Code should match
  4. Plugin ecosystem: Plugins can't extend Claude Code in useful ways without autocomplete hooks

Related Issues

Implementation Notes

The claudeignore plugin in this repository demonstrates:

  • Pattern matching with the ignore npm 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:

  1. Run /config and disable "Respect .gitignore in file picker" globally
  2. Now every project I work on exposes all gitignored files (including .env files with API keys!)
  3. Go to every other project and create a .ignore file to re-block sensitive files
  4. 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 @tasks shows 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

mattheworiordan avatar Nov 25 '25 11:11 mattheworiordan

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/5105
  2. https://github.com/anthropics/claude-code/issues/1248
  3. 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

github-actions[bot] avatar Nov 25 '25 11:11 github-actions[bot]

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.

mattheworiordan avatar Nov 25 '25 11:11 mattheworiordan

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.

JuanCS-Dev avatar Nov 25 '25 13:11 JuanCS-Dev

Strong +1.

cansar avatar Nov 26 '25 05:11 cansar

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.

edruder-block avatar Nov 26 '25 17:11 edruder-block

+1

NikkeTryHard avatar Nov 30 '25 06:11 NikkeTryHard

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.

PaulRBerg avatar Dec 15 '25 11:12 PaulRBerg

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.

eXamadeus avatar Dec 16 '25 03:12 eXamadeus

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?

mattheworiordan avatar Dec 16 '25 20:12 mattheworiordan

thanks @mattheworiordan - I was referring to the sheer length of the issue.

PaulRBerg avatar Dec 16 '25 20:12 PaulRBerg

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.

mattheworiordan avatar Jan 01 '26 07:01 mattheworiordan

@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)?

mattheworiordan avatar Jan 06 '26 09:01 mattheworiordan

Why are issues closed so aggressively (7 days after the last comment

Seconded. Issues should remain open for longer

PaulRBerg avatar Jan 06 '26 12:01 PaulRBerg

@mattheworiordan have u tried doing something like this in your .ignore?

node_modules/*
!node_modules/donotignore/

hackyon-anthropic avatar Jan 06 '26 14:01 hackyon-anthropic

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

hackyon-anthropic avatar Jan 06 '26 14:01 hackyon-anthropic

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 avatar Jan 06 '26 22:01 mattheworiordan

@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!)

hackyon-anthropic avatar Jan 06 '26 23:01 hackyon-anthropic

Ok, great, thanks @hackyon-anthropic. If you are able to confirm when it's available for download, I'd love to test it

mattheworiordan avatar Jan 07 '26 18:01 mattheworiordan

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.

hackyon-anthropic avatar Jan 12 '26 13:01 hackyon-anthropic

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.

github-actions[bot] avatar Jan 19 '26 14:01 github-actions[bot]