Managing Claude Code Config Sprawl: settings.json, MCP, Skills

Your Claude Code setup is probably scattered across six directories and nobody told you. ~/.claude/agents/, the settings.json hierarchy, MCP configs that break on reprovision — here's the audit and organization system that keeps it all together.

Claude Code's configuration surface is larger than most developers realize. By the time you've written a CLAUDE.md, configured MCP servers, installed a few skills, and tweaked permissions, you've touched at least eight distinct file locations — none of them coordinated by default. This guide gives you a repeatable system: audit everything Claude Code touches, separate shareable config from local overrides, version-control the right pieces in dotfiles and git, and write a bootstrap script so any new environment — a Daytona workspace, a fresh laptop, a cloud VM — picks up exactly where you left off.


TL;DR: Claude Code config sprawls across ~/.claude.json, ~/.claude/settings.json, ~/.claude/CLAUDE.md, ~/.claude/agents/, ~/.claude/commands/, project-level CLAUDE.md, .claude/settings.json, and .mcp.json. Run a four-command audit. Version-control global config via dotfiles symlinks; version-control project config via git. Write a bootstrap script. The whole system takes about an hour to set up and eliminates manual environment reconstruction permanently.


Why does Claude Code config sprawl happen?

Claude Code config starts simple. You add a CLAUDE.md to a project, tweak a global permission, install an MCP server for filesystem access. Each step is reasonable in isolation. The problem is structural accumulation: Claude Code separates global config from project config by design, then further separates shareable settings from local overrides. That's a sensible architecture, but without a deliberate management system, the pieces pile up across your filesystem — untracked, undocumented, and unreproducible.

The gap is documented clearly in the community thread where Claude Deck launched — a self-hosted web dashboard that a father-son team built and shipped specifically because no native management solution exists. As they described it: "the setup starts simple and then gradually sprawls across config files and directories: ~/.claude.json, ~/.claude/settings.json, .mcp.json, slash commands, agents, skills, project config, transcripts." That's a product being built around a pain point. The fact that a working dashboard shipped means the sprawl is real enough to justify significant engineering effort to address.


What files make up your complete Claude Code environment?

Understanding the full config surface is the prerequisite to managing it. Claude Code reads from and writes to these locations:

Global (user-level):

File / Directory Purpose
~/.claude.json Auth tokens, global preferences, default model
~/.claude/settings.json Permissions, hooks, environment variables, behavior flags
~/.claude/CLAUDE.md Global instructions loaded in every Claude Code session
~/.claude/agents/ Custom agent personas (subdirectories, each with a CLAUDE.md)
~/.claude/commands/ Custom slash commands (individual .md files)
~/.claude/projects/ Session transcripts, one directory per project path

Project-level:

File / Directory Purpose
CLAUDE.md Project instructions, conventions, stack details
.claude/settings.json Project-scoped permissions and hooks (commit this)
.claude/settings.local.json Local overrides, personal flags, absolute paths (never commit)
.mcp.json MCP server configuration for this project

The ~/.claude/projects/ directory grows silently. Each project accumulates .jsonl transcript files for every session. On an active machine used for multi-hour autonomous tasks, this directory can quietly reach several gigabytes. It is not config — it's history — but it's the most common source of disk surprises.


Step 1: Audit your current Claude Code config

Run these commands to get a complete picture of what you actually have:

# Global config snapshot
echo "=== ~/.claude.json keys ===" && jq 'keys' ~/.claude.json 2>/dev/null || echo "missing"
echo "=== ~/.claude/settings.json ===" && cat ~/.claude/settings.json 2>/dev/null | jq . || echo "missing"
echo "=== Global CLAUDE.md line count ===" && wc -l ~/.claude/CLAUDE.md 2>/dev/null || echo "missing"

# Custom agents and commands
echo "=== Agents ===" && ls ~/.claude/agents/ 2>/dev/null || echo "no agents directory"
echo "=== Commands ===" && ls ~/.claude/commands/ 2>/dev/null || echo "no commands directory"

# Transcript disk usage
echo "=== Transcript storage ===" && du -sh ~/.claude/projects/ 2>/dev/null || echo "no projects directory"

# Project-level config across all repos
echo "=== Project CLAUDE.md files ===" && find ~/projects -name "CLAUDE.md" 2>/dev/null | head -20
echo "=== Project .mcp.json files ===" && find ~/projects -name ".mcp.json" 2>/dev/null | head -20
echo "=== Project .claude/ directories ===" && find ~/projects -maxdepth 3 -name ".claude" -type d 2>/dev/null | head -20

The output tells you three things: what global config exists and whether it's populated; which projects have project-level config that may or may not be committed; and how much disk your transcripts are consuming. Most developers running this for the first time find MCP configs they set up months ago, CLAUDE.md files that are out of date, and a transcript directory using more disk than they expected.


Step 2: Separate shareable config from local config

The core organization principle is the same as any twelve-factor config system: some settings are for the repo and belong in version control; some settings are for your machine and must never be committed.

Commit to git:

  • CLAUDE.md (project root) — conventions, stack description, agent instructions your whole team benefits from
  • .claude/settings.json — project-level permissions and hooks that should be consistent for all contributors
  • .mcp.json — MCP server config your team needs to run the same servers

Never commit:

  • .claude/settings.local.json — personal overrides, absolute paths, local flags
  • ~/.claude.json — contains authentication tokens that rotate
  • Any file that contains an API key in plaintext, even embedded in an MCP server config block

Add this to every project's .gitignore if it isn't already there:

# Claude Code local config — never commit
.claude/settings.local.json
.claude/*.log

Anything in .claude/settings.local.json is yours alone. Anything in .claude/settings.json is shared. That boundary is the most important thing to get right, because committing a settings.local.json with an API key is the most common way this config system causes a real problem.


Step 3: Track global config in your dotfiles repo

Global Claude Code settings — ~/.claude/settings.json, ~/.claude/CLAUDE.md, your agents, your commands — should live in your dotfiles repository. The pattern is standard: move the files into a versioned directory, then symlink back to where Claude Code expects them.

# Create a claude/ subtree in your dotfiles repo
DOTFILES="${HOME}/.dotfiles"  # change to your dotfiles path
mkdir -p "${DOTFILES}/claude/agents"
mkdir -p "${DOTFILES}/claude/commands"

# Move (not copy) config files into dotfiles
mv ~/.claude/settings.json "${DOTFILES}/claude/settings.json"
mv ~/.claude/CLAUDE.md "${DOTFILES}/claude/CLAUDE.md"

# Move agents and commands
rsync -a ~/.claude/agents/ "${DOTFILES}/claude/agents/"
rsync -a ~/.claude/commands/ "${DOTFILES}/claude/commands/"

# Symlink everything back
ln -sf "${DOTFILES}/claude/settings.json" ~/.claude/settings.json
ln -sf "${DOTFILES}/claude/CLAUDE.md" ~/.claude/CLAUDE.md

# For agents: symlink each subdirectory individually
for agent_dir in "${DOTFILES}/claude/agents/*/"; do
  ln -sf "${agent_dir}" ~/.claude/agents/"$(basename "${agent_dir}")"
done

# For commands: symlink each file
for cmd_file in "${DOTFILES}/claude/commands/"*.md; do
  ln -sf "${cmd_file}" ~/.claude/commands/"$(basename "${cmd_file}")"
done

Do not symlink ~/.claude.json — it contains authentication tokens that change on re-auth and must not be committed. The pattern for organizing agent personas and command files this way is documented in community repos like Trail of Bits' claude-code-config, which structures skills as agent personas with encapsulated system prompts. For a working example of a well-organized skills collection using this structure, daymade's claude-code-skills CLAUDE.md is worth reviewing as a reference.


Step 4: Write a bootstrap script for new environments

A dotfiles symlink strategy handles the files. A bootstrap script handles the installation side — ensuring the right CLI tools, MCP servers, and Node dependencies are present on any new machine.

#!/usr/bin/env bash
# bootstrap-claude.sh — provision a Claude Code environment from dotfiles
set -euo pipefail

DOTFILES="${HOME}/.dotfiles"

echo "→ Installing Claude Code..."
npm install -g @anthropic-ai/claude-code

echo "→ Creating directory structure..."
mkdir -p ~/.claude/agents ~/.claude/commands

echo "→ Symlinking global config..."
ln -sf "${DOTFILES}/claude/settings.json" ~/.claude/settings.json
ln -sf "${DOTFILES}/claude/CLAUDE.md" ~/.claude/CLAUDE.md

echo "→ Symlinking agents..."
for agent_dir in "${DOTFILES}/claude/agents/*/"; do
  agent_name="$(basename "${agent_dir}")"
  ln -sf "${agent_dir}" ~/.claude/agents/"${agent_name}"
done

echo "→ Symlinking commands..."
for cmd_file in "${DOTFILES}/claude/commands/"*.md; do
  cmd_name="$(basename "${cmd_file}")"
  ln -sf "${cmd_file}" ~/.claude/commands/"${cmd_name}"
done

echo "→ Installing MCP servers..."
# Add your MCP server installations here. Example:
# npm install -g @modelcontextprotocol/server-filesystem
# npm install -g @modelcontextprotocol/server-github

echo ""
echo "✓ Claude Code environment ready."
echo "  Run 'claude' to authenticate and complete setup."

Commit this script to your dotfiles repository. From that point, reprovisioning a Claude Code environment on any machine — a new laptop, a cloud VM, a Daytona workspace — takes under five minutes. The official Claude Code common workflows documentation covers additional session management patterns that pair well with this bootstrap approach.


How do you verify your Claude Code config is clean?

After completing the audit and reorganization, verify three things explicitly:

1. No secrets in version-controlled files

# Scan your dotfiles claude/ subtree for anything that looks like a credential
grep -rE "sk-ant-|ANTHROPIC_API_KEY|Bearer [A-Za-z0-9]|ghp_" \
  ~/.dotfiles/claude/ 2>/dev/null \
  && echo "WARNING: potential credential found" \
  || echo "clean"

2. Symlinks resolve correctly

ls -la ~/.claude/settings.json ~/.claude/CLAUDE.md
# Expected output: ~/.claude/settings.json -> /home/you/.dotfiles/claude/settings.json

3. Project-level config is tracked in git

# In any project repo with Claude Code config
git ls-files --error-unmatch CLAUDE.md .claude/settings.json .mcp.json 2>/dev/null \
  && echo "all tracked" \
  || echo "WARNING: some files are untracked"

If a project's .claude/settings.json is untracked, it was almost certainly created locally and never added to git. Use git add .claude/settings.json explicitly — review what's in it before adding, in case local overrides ended up in the wrong file.


Troubleshooting: Common Claude Code config problems

Settings not loading after symlinking Claude Code reads settings at process startup. If you symlinked a file that previously existed as a regular file, the old inode may have been cached. Restart Claude Code completely after creating or changing symlinks.

MCP server config breaks on a new machine Most MCP configs include absolute paths — to node_modules, to a workspace root, or to a binary. Those paths are machine-specific and break on reprovision. Fix them by using ${HOME} or relative paths, or by using npx -y so the server installs on demand:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "${HOME}/projects"]
    }
  }
}

Agents not recognized after dotfiles setup Agent directories must be named subdirectories under ~/.claude/agents/, each containing a CLAUDE.md file. A flat .md file at the top level of ~/.claude/agents/ is not recognized as an agent. Verify you're symlinking directories, not files:

# Correct structure
ls -la ~/.claude/agents/
# Should show: my-agent -> /home/you/.dotfiles/claude/agents/my-agent/

ls ~/.claude/agents/my-agent/
# Should show: CLAUDE.md

Transcript directory consuming too much disk

# Find the 10 largest project transcript directories
du -sh ~/.claude/projects/*/ | sort -h | tail -10

# Transcripts for old projects are safe to delete
# They're session history, not configuration
rm -rf ~/.claude/projects/"$(echo '/path/to/old/project' | sed 's|/|%2F|g')"

Transcripts are needed for session resumption but not for environment reproducibility. Back them up if you want history preserved; otherwise, consider excluding ~/.claude/projects/ from any backup tool that charges by storage volume.


How Grass and Daytona Make This Workflow Better

The audit, organization, and version-control system above is fully tool-agnostic — it works on any machine running any OS. But it solves portability one dimension at a time: you get reproducible config, but applying it to a new environment still requires manual steps. Daytona and Grass close that loop.

Daytona: bake your Claude Code environment into a workspace definition

Daytona provisions cloud development environments from a devcontainer.json or Dockerfile committed to your repository. Once you've version-controlled your Claude Code config following the steps in this guide, you can run your bootstrap script as a postCreateCommand in the devcontainer definition:

// .devcontainer/devcontainer.json
{
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "postCreateCommand": "bash .devcontainer/bootstrap-claude.sh",
  "remoteEnv": {
    "ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}"
  }
}

Every Daytona workspace you spin up for that project runs the bootstrap automatically. Your global settings.json symlinks are created, your agents and commands are linked in, MCP servers are installed — Claude Code is ready without any manual steps. Commit the bootstrap script, and the environment definition is as reproducible as the code itself. The complete guide to setting up Claude Code on a Daytona workspace covers the full workspace provisioning flow, including authentication and Tailscale for remote access.

Grass: persistent access and approval forwarding that survives environment rebuilds

When your Claude Code environment is running on a Daytona cloud workspace, Grass is the access layer that keeps you connected to it regardless of where you are. Install the @grass-ai/ide CLI in your devcontainer definition alongside the Claude Code bootstrap:

# Add to bootstrap-claude.sh
echo "→ Installing Grass..."
npm install -g @grass-ai/ide

Every Daytona workspace you provision becomes immediately reachable via grass start — no SSH config, no VPN tunnel, no remembered IP address. When an agent running on the workspace hits a permission request (a bash command, a file write), Grass forwards the approval modal to your phone. You approve or deny from wherever you are; the agent continues or stops accordingly.

The config management system you've built makes Grass more valuable: if you ever reprovision a workspace after environment drift or a dependency conflict, your version-controlled bootstrap gets you back to a clean state in minutes — and Grass reconnects automatically. You never have to reconstruct config by hand to get back to a working session. The guide to setting up Grass with a Daytona remote server walks through the exact configuration, including permission forwarding from a cloud-based Claude Code session. For long-running autonomous tasks specifically, running Claude Code unattended covers the full approval gate and session monitoring workflow.

Grass is recommended for the Daytona layer but not required for the config management system — every step above works on any environment without it.


FAQ

What files does Claude Code use for configuration? Claude Code reads from eight locations: ~/.claude.json (global auth and preferences), ~/.claude/settings.json (global behavior settings), ~/.claude/CLAUDE.md (global instructions loaded in every session), ~/.claude/agents/ (custom agent personas), ~/.claude/commands/ (custom slash commands), CLAUDE.md at the project root (project instructions), .claude/settings.json (project-level settings), and .mcp.json (MCP server config). The ~/.claude/projects/ directory stores session transcripts but is not a config source.

Which Claude Code config files should I commit to git? Commit CLAUDE.md (project root), .claude/settings.json, and .mcp.json. Never commit .claude/settings.local.json or ~/.claude.json — both contain machine-specific data or auth tokens. Add .claude/settings.local.json to your project .gitignore to prevent accidental commits.

How do I make my Claude Code environment reproducible on a new machine? Version-control your global config via a dotfiles repo: move ~/.claude/settings.json, ~/.claude/CLAUDE.md, and your agents/commands directories into a versioned directory, then symlink back. Write a bootstrap script that installs Claude Code, creates the symlinks, and installs required MCP servers. Commit the script. On any new machine, clone your dotfiles and run it — the full environment is restored in under five minutes.

How do I stop Claude Code transcripts from consuming too much disk? Session transcripts live in ~/.claude/projects/, organized by project path as URL-encoded directory names. Run du -sh ~/.claude/projects/*/ to identify the largest. Transcript directories for inactive projects are safe to delete — they contain session history, not configuration, and Claude Code functions normally without them. Exclude ~/.claude/projects/ from cloud backup tools if disk or storage cost is a concern.

Why do MCP server configs break when I move to a new machine? Most MCP configs reference absolute paths: the path to node, an absolute workspace directory, or a hardcoded home path. Those paths don't exist on a new machine. Fix it by using npx -y to install the MCP server on demand rather than referencing a pre-installed binary, and use ${HOME} or relative paths in any argument that references a directory. Test your .mcp.json after any reprovision by running claude in a project that uses it and verifying the MCP server initializes correctly.


Published by Grass — a machine built for AI coding agents. Claude Code, Codex, and Open Code run on an always-on cloud VM, accessible from your laptop, your phone, or an automation.