Skip to content

Symlinks Manager

Cross-platform dotfiles symlink manager with layered architecture.

Commands

All symlinks commands are run via Task from the dotfiles root directory. The tool uses uv run internally for project-local execution.

Deploy symlinks for current platform (common + platform layers). Additive only - creates new symlinks without removing existing ones.

task symlinks:link         # Create symlinks (safe, no removal)

Use when adding new dotfiles to create their symlinks without disturbing existing ones.

Complete refresh - removes all symlinks and recreates them.

task symlinks:relink       # Full refresh (removes + links)

Use after removing files from dotfiles repo or when you need a clean slate.

task symlinks:check

Verify symlink integrity.

task symlinks:check        # Find broken symlinks

Shows broken symlinks in home directory.

task symlinks:show

Display current symlinks.

task symlinks:show         # Show all symlinks

Remove all symlinks.

task symlinks:unlink         # Remove all symlinks

Direct Usage (Advanced)

If needed, run the tool directly from dotfiles root:

uv run symlinks link common
uv run symlinks link macos
uv run symlinks relink macos
uv run symlinks check

Pass -v/--verbose before the subcommand to show individual file operations:

uv run symlinks -v link macos      # Show each file as it's linked
uv run symlinks -v relink macos    # Show each file during full refresh

Architecture

The symlinks tool uses a layered architecture: common base + platform overlay.

Common base (common/):

  • Shared configs across all platforms
  • .zshrc, .config/nvim, .config/tmux, etc.
  • Linked first

Platform overlay (macos/, wsl/, archlinux/, linux/):

  • Platform-specific configs
  • Overrides or extends common configs
  • Linked second (can override common)
  • Optional per layer: a minimal platform like linux ships only a shell overlay (shell/linux/) and no configs/linux/ or apps/linux/. link and relink skip a missing layer instead of erroring; only a platform absent from every layer (a typo) fails.

Conflict handling:

  • File vs file: Platform overlay wins
  • Directory vs directory: Merged (both symlinked)
  • File vs directory: Error (must resolve manually)

Special Directory Handling

The symlinks manager maps apps/ and shell/ to specific target directories rather than $HOME, using the same create_symlinks function with a custom target_dir.

Shell scripts (apps/common/menu, apps/common/notes, etc.):

  • Symlinked to ~/.local/bin/
  • Examples: menu, notes, patterns, aws-profiles

Shell source files (shell/common/functions.sh, shell/common/aliases.sh, shell/{platform}/{platform}.sh):

  • Symlinked to ~/.local/shell/
  • Common: functions.sh + aliases.sh on all platforms
  • Platform-specific: macos.sh, archlinux.sh, wsl.sh, linux.sh
  • These are shell code (functions + aliases), not config — ~/.local/shell/ is intentional

Go apps (toolbox, sesh):

  • Installed from GitHub via go install (defined in packages.yml)
  • Development in ~/tools/toolbox/
  • NOT managed by symlinks - binaries go to ~/go/bin/

Personal CLI tools (theme, font):

  • Installed via custom installers that clone to ~/.local/share/
  • Symlink ~/.local/share/{tool}/bin/{tool}~/.local/bin/{tool}
  • Development in ~/tools/theme/, ~/tools/font/
  • NOT managed by symlinks manager - have their own installers

Usage

The symlinks tool runs via uv run from the dotfiles root directory. Use Task commands for the best experience:

task symlinks:link     # Create symlinks (additive, safe)
task symlinks:relink   # Full refresh (removes + recreates)
task symlinks:check    # Verify symlinks
task symlinks:show     # Display current symlinks

No installation required - uv run executes the tool in-place.

Use task symlinks:link (additive, safe):

  • Adding new files to dotfiles repo
  • Adding new dotfile directories
  • After fresh install or setup

Use task symlinks:relink (full refresh):

  • Removing files from dotfiles repo
  • Moving files between directories
  • Fixing broken or stale symlinks
  • When you need a clean slate
  • Changing platform (macos → wsl, etc.)
  • Symlink errors or broken links

Symptom of outdated symlinks: "module not found" errors in Neovim after creating new files in common/.config/nvim/lua/ directories.

Testing

The symlinks tool has comprehensive pytest test suite.

cd ~/dotfiles/symlinks
uv run python -m pytest tests/ -v     # Run all tests
uv run python -m pytest tests/test_core.py          # Unit tests
uv run python -m pytest tests/test_integration.py  # Integration tests

Tests cover:

  • Link creation and unlinking
  • Conflict detection
  • Platform overlay logic
  • Cross-platform path resolution
  • Edge cases (loops, permissions)

Configuration

Exclusion patterns in symlinks/core.py (EXCLUDE_PATTERNS constant):

Excluded by default:

  • .git/ directories
  • .DS_Store files
  • __pycache__/ directories
  • .pytest_cache/ directories
  • .venv/ virtual environments

Platform-specific exclusions: Each platform config can define additional exclusions.

Critical Bugs to Avoid

Substring Matching

Problem: Pattern .git/ incorrectly excluded .gitconfig

Fix: Check for /.git/ or starts with .git/, not substring match

See: docs/learnings/directory-pattern-matching.md

Relative Path Calculation

Problem: Manual path calculation broke 122 symlinks

Fix: Use Python stdlib Path.relative_to(walk_up=True) (Python 3.12+)

See: docs/learnings/relative-path-calculation.md

Cross-Platform Files

Problem: Some files needed on all platforms weren't symlinked

Fix: Test edge cases - .gitconfig, .gitignore, .gitattributes should NEVER be excluded

See: docs/learnings/cross-platform-symlink-considerations.md

Troubleshooting

Symlinks not created:

  • Run with verbose flag: uv run symlinks -v link macos
  • Check for permission errors
  • Verify source files exist in dotfiles repo

Broken symlinks:

  • Run task symlinks:check to find them
  • Remove: find ~ -type l ! -exec test -e {} \; -delete
  • Re-run: task symlinks:link

File conflicts:

  • Manual resolution required
  • Check conflict error message for paths
  • Decide: keep existing file or use dotfiles version
  • Move existing file to backup, then relink

Module not found in Neovim:

  • Added new files in common/.config/nvim/lua/?
  • Run: task symlinks:link
  • Restart Neovim

Development

The package lives at symlinks/ in the repo root as part of the main dotfiles project — cli.py for the Typer interface, core.py for all logic. Dependencies (typer, rich) and entry point are defined in the root pyproject.toml. Run from anywhere with uv run symlinks.

Dependencies: typer (CLI framework), rich (console output)

Python version: 3.12+ (requires Path.relative_to(walk_up=True))

See Also