Keep Your Claude Code Agents Out of the Team's Repo

⏱ 7 min read

Your Claude Code setup is yours. Your team's repo shouldn't have to carry it.

You've spent time building up a nice set of Claude Code agents and skills. Custom reviewers, project-specific helpers, the kind of thing that makes your day a bit smoother. Then you open a pull request and realise: that .claude/ folder is sitting right there in the diff, ready to land in a shared repository that your whole team owns.

Now you have a decision to make, and neither option feels great.

The Problem With the Obvious Choices 🔗

You can commit your .claude/ folder to the repo. It works. But now you're adding personal AI tooling to a shared codebase that your team didn't ask for, and the next time a colleague clones the repo they get your configuration whether they want it or not. That's not a disaster, but it's noise - and noise compounds.

The other option is to leave it local. Simple. Except local means you lose it the moment you switch machines, rebuild your environment, or hand this work off to someone else. Local-only configuration doesn't travel with you.

There's a third path that most people don't think of: keep your agents and skills in a separate, private repository and use symbolic links to make Claude discover them as if they lived inside each project.

The Setup: One Config Repo, Many Projects 🔗

The idea is straightforward. Instead of scattering .claude/ folders across every repo you work in, you maintain a single claude-configs repository that mirrors your project structure. Each project gets a folder in there, and your actual project repos get a symlink that points back to it.

Let's take a childish example to make it concrete. Say you have two projects: my-api and my-worker. Your layout ends up looking like this:

workspace/
  my-api/                  ← shared, committed, team-owned
  my-worker/               ← shared, committed, team-owned
  claude-configs/          ← private, yours, committed to your own repo
    my-api/
      .claude/
        agents/
        skills/
    my-worker/
      .claude/
        agents/
        skills/

Claude Code sees my-api/.claude/agents/ and reads your agents from it. It has no idea - and doesn't need to know - that the folder is actually a symlink into claude-configs.

Step 1 - Create the Config Repo 🔗

Start by creating the structure and moving your existing agents and skills over.

mkdir claude-configs
cd claude-configs
git init

mkdir -p my-api/.claude/agents
mkdir -p my-api/.claude/skills
mkdir -p my-worker/.claude/agents
mkdir -p my-worker/.claude/skills

Then copy your existing agents and skills into their new home (run these from your workspace/ root, not from inside claude-configs):

cp -r my-api/.claude/agents/* claude-configs/my-api/.claude/agents/
cp -r my-api/.claude/skills/* claude-configs/my-api/.claude/skills/

Now the source of truth lives in claude-configs. Your projects will just point to it.

Remove the real folders from your project and replace them with symbolic links.

On Linux, macOS, or WSL:

cd my-api

# Remove the original folders (content is already in claude-configs)
rm -rf .claude/agents .claude/skills

# Create the symlinks
ln -s ../../claude-configs/my-api/.claude/agents .claude/agents
ln -s ../../claude-configs/my-api/.claude/skills .claude/skills

On Windows (PowerShell - requires Developer Mode or Administrator):

cd my-api

Remove-Item -Recurse -Force .claude\agents, .claude\skills

New-Item -ItemType SymbolicLink -Path ".claude\agents" -Target "..\..\claude-configs\my-api\.claude\agents"
New-Item -ItemType SymbolicLink -Path ".claude\skills" -Target "..\..\claude-configs\my-api\.claude\skills"

Use absolute paths on Windows if relative paths give you trouble. The target has to exist before you create the link, so make sure claude-configs is set up first.

Here's the part that trips people up. If you add .claude/agents to .gitignore, that file gets committed and your teammates see it. That's exactly what we're trying to avoid.

The right tool is .git/info/exclude. It behaves like .gitignore but it's machine-local and never committed. Your colleagues will never know it exists.

# Inside my-api
echo ".claude/agents" >> .git/info/exclude
echo ".claude/skills" >> .git/info/exclude

On Windows, Git sometimes treats directory symlinks as a single file entry rather than a directory. Add both forms to cover your bases:

.claude/agents
.claude/agents/
.claude/skills
.claude/skills/

Then verify:

git status

The agents/ and skills/ folders should not appear. If they do, double-check the entries in .git/info/exclude - a typo there is the usual culprit.

Step 4 - Commit the Config Repo 🔗

cd claude-configs
git add .
git commit -m "Add Claude agents and skills for my-api and my-worker"
git remote add origin <your-private-repo-url>
git push -u origin main

You now have version history, a backup, and a clean way to reproduce your setup on any machine. Clone claude-configs, re-run the ln -s commands, and you're back to where you were.

Day-to-Day Workflow 🔗

Once it's running, you mostly forget it's there. You edit files in claude-configs, commit inside that repo, and the changes are immediately visible through the symlink. No sync step, no copy-paste.

Action Where
Edit an agent or skill claude-configs/my-api/.claude/agents/
Save changes git commit inside claude-configs
Use on a new machine Clone claude-configs, re-run the ln -s commands
Add a new project Create the folder in claude-configs, symlink, exclude

Why .git/info/exclude and not .gitignore 🔗

This deserves a proper answer because the difference matters.

.gitignore .git/info/exclude
Committed to repo Yes No
Affects teammates Yes No
Reversible without a commit No Yes

.gitignore is a shared agreement between everyone on the team. When you add something there, you're making a statement on behalf of the project. .git/info/exclude is for exactly this kind of case: personal, local configuration that is nobody else's business.

A Note for Windows Users 🔗

Windows requires Developer Mode or an Administrator terminal to create symlinks. If you get an access error, that's why.

You also need to make sure Git is configured to handle symlinks correctly:

git config --global core.symlinks true

Without this, Git may check out a symlink as a plain text file containing the target path as a string. The folder won't exist, Claude won't find anything, and you'll spend twenty minutes wondering what went wrong. Set it once, globally, and move on.

Credit 🔗

This pattern is adapted from Tamir Dresher's approach to using GitHub Copilot's agent framework without touching shared repositories. The same idea maps cleanly onto Claude Code's .claude/agents and .claude/skills structure.


Your tooling is part of how you work, not part of what the team ships. Keeping those things separate has always been the right instinct - symlinks just make it practical.

Enjoyed this post?

Join my newsletter and get notified about new posts on .NET and the world around it.