Imported from justmytwospence/dotfiles (
shell/.claude/skills/dotfiles-sync/SKILL.md). Install upstream withnpx skills add justmytwospence/dotfiles --skill dotfiles-sync. Copyright stays with the author.
Dotfiles sync
~/dotfiles is stowed on two machines. Every commit/push/stow on the Mac must be
followed by a pull/restow on the NUC, or the two drift.
| Mac (primary) | NUC | |
|---|---|---|
| Host | local | spencer@nuc (Debian, x86_64) |
| Repo | ~/dotfiles |
~/dotfiles |
| Branch | main |
main |
| Remote | ssh://git@github.com/justmytwospence/dotfiles.git |
same, git@ form |
| Stowed packages | shell, osx |
shell, nuc |
| GNU Stow | 2.4.1 | 2.3.1 |
Sequence
Do the local half first, then the remote half. Never push from the NUC.
# Mac
cd ~/dotfiles
git add <only the files for this change> # atomic; leave unrelated drift alone
git commit
git push origin main
dotfiles-restow shell # add osx if the change touched osx/
# NUC
ssh -o BatchMode=yes -o ConnectTimeout=10 spencer@nuc '
cd ~/dotfiles &&
git pull --rebase --autostash &&
~/dotfiles/shell/.local/bin/dotfiles-restow shell nuc
'
Invoke the script by its repo path on the NUC. ~/.local/bin/dotfiles-restow is
itself a stowed symlink, so the repo path is the one that always works — including
on a host where stow has never successfully run.
Then verify the change actually landed: git log -1 --oneline, plus a grep of
whichever file you changed through its stowed path (~/.claude/settings.json,
not ~/dotfiles/shell/...), so you confirm the symlink resolves.
Always restow through dotfiles-restow
Never call stow -R directly. GNU Stow aborts the entire operation when any
target is a file it does not own — one unmanaged .zshrc blocks every other link in
the package. That failure is quiet in the worst way: targets that are already
symlinks keep tracking the repo, so content edits still land and the host looks
synced, while added and removed files silently do not propagate.
shell/.local/bin/dotfiles-restow retries with the conflicting paths excluded, so
everything else stows, and reports what it skipped. Its exit codes:
| Exit | Meaning | What to do |
|---|---|---|
| 0 | fully stowed | nothing |
| 1 | stowed except the reported conflicts | relay the conflict list to the user |
| 2 | stow failed for some other reason | stop and show the raw stow output |
Exit 1 is not a failure of the sync — everything except the listed targets is linked, and new files did propagate. Do not treat it as a reason to retry, and do not report the sync as broken. Do surface the list; those files are silently diverging between hosts.
Resolving a conflict
Only when the user asks. Each conflict is a real file on that host whose contents differ from the repo, so resolving it means deciding which copy wins:
- Repo wins:
mv ~/PATH ~/PATH.local && dotfiles-restow <pkg>. The backup keeps the host's version recoverable. Show the diff first. - Host wins: the file is genuinely machine-specific. Leave it, or dotfilize it properly under a host-specific path.
Never use stow --adopt. It resolves the conflict backwards — overwriting the repo
with that host's copy, committing the drift, and reporting success.
No conflicts are outstanding on either host. Both packages restow at exit 0, so a non-zero exit is new information, not the known baseline. Three of the four that used to exist were resolved in ways worth not undoing:
~/.config/herdr/config.tomlis host-specific by design. The two machines need genuinely different herdr configs, so it lives inosx/andnuc/rather thanshell/. Do not merge them back into one.Library/Application Support/Claude/claude_desktop_config.jsonis deliberately not stowed — seeosx/.stow-local-ignore. Claude Desktop rewrites it with account and paired-device UUIDs and local work paths, and this repo is public. The tracked copy is a reference snapshot; the live file stays a real file.~/.zshrcon the NUC had a redundantcc-clipPATH block prepended, backed up to~/.zshrc.pre-stow. If cc-clip re-adds it, it will be editing the symlink and so writing into the repo -- move the block to~/.zshrc.local, which.zshrcalready sources, rather than letting it sit in the stowed file.
Pull failures on the NUC
--rebase --autostash is deliberate. The NUC accumulates machine-local uncommitted
drift in tracked files, because tools write through the symlinks into the repo —
Claude Code parks host-specific keys like fastMode in shell/.claude/settings.json
there. Autostash carries that across the pull. Do not commit it from the NUC and do
not git checkout -- it away; it is that machine's real state.
If the rebase or the autostash-reapply hits a conflict, the NUC is left mid-operation with a dirty tree. Do not try to resolve it blind over SSH. Back out and hand it to the user:
ssh spencer@nuc 'cd ~/dotfiles && git rebase --abort 2>/dev/null; git status --short'
If the autostash was already applied and conflicted, the stash still exists —
git stash list — so nothing is lost. Report the state and stop.
If the NUC is unreachable, say so explicitly and report the sync as incomplete. Never let a failed SSH read as success.
SSH noise
The connection prints a post-quantum key-exchange warning and remote port forwarding failed lines on stderr. Both are expected. Filter them so they do not read as errors:
... 2>&1 | grep -v '^\*\*\|^Warning: remote'