One Skills Brain for Codex, Claude, Cursor, and Copilot with Chezmoi
When you use multiple coding agents across multiple machines, the same prompt often behaves differently for a simple reason: your agent skills and MCP wiring drift over time.
Typical failure modes:
- a skill is updated in one tool but not the others
- MCP servers are configured differently per machine
- one tool reads user-level config, another reads project-level config, and behavior diverges
The initial problem is not just dotfiles sync. It is maintaining one consistent agent behavior contract across Codex, Claude, Cursor, and Copilot despite different config formats and file locations.
The solution in this guide is to separate concerns:
- one canonical skills directory:
~/.agent-skills/skills - one canonical MCP intent (shared definitions rendered per tool)
- one dotfiles manager (chezmoi) to materialize tool-native adapters
Why this works:
- skills stay identical because they live in one place
- each tool keeps its native schema, so updates are compatible with official behavior
- chezmoi templates and symlinks remove manual copy/paste and reduce drift
- path differences are explicit and auditable
All path references below follow:
Path contract (from agent-storage-map)
Codex
User config:~/.codex/config.toml
Project config:.codex/config.toml
Skills:.agents/skillsandSKILL.mdfoldersClaude Code
Main MCP config:~/.claude.json
Dedicated MCP file:~/.claude/mcp_servers.json(lower precedence than~/.claude.json)
Settings:~/.claude/settings.json,.claude/settings.json,.claude/settings.local.json
Sub-agents:~/.claude/agents/,.claude/agents/Cursor
MCP config:~/.cursor/mcp.json,.cursor/mcp.json
Skills:~/.cursor/skills/,.cursor/skills/
Compatibility skills paths include~/.codex/skills/,~/.claude/skills/GitHub Copilot
MCP config:~/.copilot/mcp-config.json
Skills:~/.copilot/skills/,.copilot/skills/
Why symlinks (not hard links)
Use symlinks for shared skills directories.
- explicit target path (
readlink) - works natively with chezmoi
symlink_files - easier operationally than hard links for directory trees
Chezmoi source tree (concrete)
This is the chezmoi source-state tree ($(chezmoi source-path)):
~/.local/share/chezmoi/
├── dot_agent-skills/
│ ├── README.md
│ ├── skills/
│ │ ├── commit-style/
│ │ │ └── SKILL.md
│ │ └── test-triage/
│ │ └── SKILL.md
│ └── mcp/
│ ├── codex-mcp.toml.tmpl
│ ├── generic-mcp.json.tmpl
│ └── copilot-mcp.json.tmpl
├── dot_codex/
│ ├── config.toml.tmpl
│ └── symlink_skills.tmpl
├── private_dot_claude.json.tmpl
├── dot_claude/
│ └── settings.json.tmpl
├── dot_cursor/
│ ├── mcp.json.tmpl
│ └── symlink_skills.tmpl
└── dot_copilot/
├── mcp-config.json.tmpl
└── symlink_skills.tmpl
Chezmoi attribute reminders:
-
dot_-> leading dot in target -
private_-> stricter permissions in target -
symlink_-> create a symlink target -
.tmpl-> template rendering
Command set
First machine
chezmoi init git@github.com:<you>/<dotfiles>.git
cd "$(chezmoi source-path)"
# Optional: import existing files
chezmoi add ~/.codex/config.toml
chezmoi add ~/.claude.json
chezmoi add ~/.claude/settings.json
chezmoi add ~/.cursor/mcp.json
chezmoi add ~/.copilot/mcp-config.json
chezmoi diff
chezmoi apply -v
git add .
git commit -m "feat: unify skills and mcp adapters"
git push
Additional machines
chezmoi init git@github.com:<you>/<dotfiles>.git
chezmoi update -v
Daily flow
chezmoi edit ~/.codex/config.toml
chezmoi diff
chezmoi apply -v
Shared skills content
Create all skills once in dot_agent-skills/skills.
Example dot_agent-skills/skills/commit-style/SKILL.md:
# commit-style
## Goal
Create clear conventional commits.
## Rules
- Keep subject under 50 chars.
- Use imperative present tense.
- Explain user impact in body.
After apply, this becomes ~/.agent-skills/skills/... and is your canonical skill bank.
Shared MCP templates
Keep one MCP intent and render per tool schema.
dot_agent-skills/mcp/codex-mcp.toml.tmpl
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
[mcp_servers.git]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
dot_agent-skills/mcp/generic-mcp.json.tmpl (Claude/Cursor style)
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
},
"git": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
}
}
}
dot_agent-skills/mcp/copilot-mcp.json.tmpl
{
"mcpServers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
},
"git": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
}
}
}
Per-tool templates
1) Codex
dot_codex/config.toml.tmpl
model = "gpt-5-codex"
approval_policy = "on-request"
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
[mcp_servers.git]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
dot_codex/symlink_skills.tmpl
{{ .homeDir }}/.agent-skills/skills
Result: ~/.codex/skills -> ~/.agent-skills/skills
Use this mainly as a compatibility anchor for tools that read Codex-style skills paths.
For Codex itself, keep project skills in .agents/skills when you want repository-local behavior.
2) Claude Code
private_dot_claude.json.tmpl
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
},
"git": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
}
}
}
dot_claude/settings.json.tmpl
{
"permissions": {
"allow": [],
"deny": []
}
}
For project-level MCP, use .mcp.json with the same filesystem path.
For agent logic, prefer documented .claude/agents/ over .claude/skills/.
3) Cursor
dot_cursor/mcp.json.tmpl
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
},
"git": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
}
}
}
dot_cursor/symlink_skills.tmpl
{{ .homeDir }}/.agent-skills/skills
Result: ~/.cursor/skills -> ~/.agent-skills/skills
4) GitHub Copilot
dot_copilot/mcp-config.json.tmpl
{
"mcpServers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
},
"git": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
}
}
}
dot_copilot/symlink_skills.tmpl
{{ .homeDir }}/.agent-skills/skills
Result: ~/.copilot/skills -> ~/.agent-skills/skills
Validation
readlink ~/.codex/skills
readlink ~/.cursor/skills
readlink ~/.copilot/skills
ls ~/.agent-skills/skills
chezmoi status
chezmoi diff
chezmoi verify
Expected: all symlinks resolve to ~/.agent-skills/skills, and each MCP file targets that same path.
Feature parity with skills-sync
I reviewed skills-sync feature usage and mapped it to chezmoi primitives.
Single source workspace
Chezmoi equivalent: one source state with canonicaldot_agent-skills/skills.Symlink distribution
Chezmoi equivalent:symlink_*.tmplentries per tool.Profile switching (
use <profile>)
Chezmoi equivalent:.chezmoidata/profiles.yaml+ template conditionals.Runtime materialization (
sync)
Chezmoi equivalent:chezmoi apply -v.Seed content (
init --seed)
Chezmoi equivalent: commit baseline skills/templates in source state.Upstream ingestion (
add-upstream,add-skill)
Chezmoi equivalent:external_or scriptedrun_onchange_sync jobs.Drift/inventory (
agents drift,agents inventory)
Chezmoi equivalent:chezmoi status/diff/verifyplus symlink checks.
Profile-aware template example
Create .chezmoidata/profiles.yaml:
active_profile: personal
profiles:
personal:
dev_root: "~/Developer"
work:
dev_root: "~/Work"
Use in template:
{{- $p := index .profiles .active_profile -}}
[mcp_servers.git]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ $p.dev_root }}"]
Switch profile by changing active_profile, then:
chezmoi apply -v
Upstream sync example
run_onchange_after_50-sync-upstream-skills.sh.tmpl
#!/usr/bin/env bash
set -eu
ROOT="$HOME/.agent-skills/skills/upstream"
mkdir -p "$ROOT"
sync_repo() {
name="$1"
url="$2"
dir="$ROOT/$name"
if [ -d "$dir/.git" ]; then
git -C "$dir" pull --ff-only
else
git clone "$url" "$dir"
fi
}
sync_repo "matlab_skills" "https://github.com/matlab-deep-learning/llm-agents-with-mcp.git"
Apply with:
chezmoi apply -v
Security and portability notes
- Keep tokens in env vars, not committed config files.
- Use
private_for files that may carry sensitive values. - Use
{{ .homeDir }}in templates for cross-machine portability.
This pattern gives one skill bank, one MCP intent, four adapters, and minimal drift.
Cheers,
Lukas
Top comments (0)