Skip to content

Make Claude Code config reproducible across hosts + cut permission prompts#61

Merged
GregHilston merged 5 commits into
masterfrom
chore/claude-config-reproducible
Jun 24, 2026
Merged

Make Claude Code config reproducible across hosts + cut permission prompts#61
GregHilston merged 5 commits into
masterfrom
chore/claude-config-reproducible

Conversation

@GregHilston

Copy link
Copy Markdown
Owner

Make Claude Code config reproducible across hosts + cut permission prompts

Why

A review of the Claude Code Insights report (1,528 messages / 134 sessions over 36
days) flagged three friction areas: permission prompts (123 rejections + 430 failed
commands), repeated context re-discovery across many repos, and edit churn.

Digging into how ~/.claude is actually wired surfaced the real root cause: the files
that would carry any of those fixes to my other hosts weren't under nix at all.

File Before After
~/.claude/commands, skills nix symlink ✅ unchanged ✅
~/.claude/CLAUDE.md manual symlink (missing on fresh hosts) nix-managed ✅
~/.claude/settings.json plain file, not in repo nix-managed ✅
~/.claude/hooks/ plain files, not in repo nix-managed ✅

What changed

Reproducibility plumbing

  • claude.nix now symlinks five ~/.claude targets from the repo via a link_repo
    helper. It refreshes existing symlinks, creates missing ones, and refuses to
    clobber a pre-existing real file
    — it warns loudly and skips so the file can be
    migrated first (non-fatal, so deploys never break).
  • Symlinks point into the repo (writable), not /nix/store, so Claude's own
    runtime writes to settings.json keep working — they just show up as git diffs.
  • just setup-claude brought to parity for non-nix hosts (same five targets + guard).
  • Migrated this host's real settings.json + hooks/ into dot/claude/.claude/
    (force-added past the global .claude/ gitignore). No secrets in either file.

Fewer permission prompts (friction #1)

  • Added 15 data-driven allowlist entries, mined from ~29k Bash calls across my
    transcripts — only safe/read-only commands that aren't already auto-allowed:
    nix build/eval/fmt, docker compose logs/ps/config, npm/pnpm typecheck & test,
    npm view/root. Mutating/arbitrary-exec commands (git push, ssh, installs, docker compose exec, interpreters) were deliberately excluded.

Leaner context (friction #2 & #3)

  • Trimmed the global CLAUDE.md (it loads in every repo): dropped the verbose
    Toolbox enumeration down to a one-line pointer, and added a File Inspection nudge to
    prefer Read/Grep/Glob over cat/sed for file reads.
  • Documented the new symlink targets + clobber guard in toolbox/CLAUDE.md, and added
    a per-repo /init pattern so other repos (ccs, home-lab, …) get their own tight
    project map instead of being re-derived each session.

Verification

  • nix fmt . — clean.
  • nix build .#darwinConfigurations.moria.system --dry-run — evaluates, 5 derivations.
  • link_repo unit-tested for all three cases (symlink → refresh, missing → create,
    real file → WARN + preserve).
  • Live ~/.claude/{settings.json,hooks,CLAUDE.md} confirmed as repo symlinks; settings
    parses as valid JSON with the new entries; RTK hook still resolves and is executable.

Notes

  • isengard doesn't import programs/tui, so it won't receive this (unchanged, out of scope).
  • Pre-existing unrelated working-tree changes (omlx, pi, dungeon, homebrew) were left
    untouched and are not part of this PR.

Extend claude.nix (and the non-nix setup-claude recipe) to symlink five
~/.claude targets from the repo instead of just commands/skills. Adds a
link_repo guard that refreshes existing symlinks, creates missing ones, and
refuses to clobber a pre-existing real file (warns and skips so it can be
migrated first). Symlinks point into the repo (writable) so Claude's runtime
writes to settings.json keep working.
…allowlist

Bring the previously-untracked ~/.claude/settings.json and hooks/ under repo
management (force-added past the global .claude/ gitignore). Add 15 data-driven
safe/read-only allowlist entries mined from transcripts (nix build/eval/fmt,
docker compose logs/ps/config, npm/pnpm typecheck & test, npm view/root) to cut
permission prompts across hosts.
…init pattern

Global CLAUDE.md loads in every repo, so drop the verbose Toolbox enumeration
(covered by toolbox/CLAUDE.md) down to a one-line pointer, and add a File
Inspection nudge to prefer Read/Grep/Glob over cat/sed for file reads. Update
toolbox/CLAUDE.md 'How they reach each host' for the five managed targets +
clobber guard, and add a per-repo /init pattern note.
Native exporters scraped by the home-lab Prometheus over host.docker.internal (a containerised exporter only sees the OrbStack Linux VM):
- node_exporter - host CPU/disk/net/load/filesystem
- macmon - Apple-Silicon temp/power/GPU/RAM
- glances - native system-monitor web UI, replacing the container
… dungeon

Runs the exporters as launchd user agents on dungeon (bind 0.0.0.0 so the OrbStack VM can scrape them via host.docker.internal):
- node-exporter :9100 (+ textfile collector dir for battery)
- macmon serve :9101 (default 9090 collides with Prometheus)
- glances -w :61208 (same port the removed container used)
- mac-battery-textfile.sh: every 60s writes node_battery_percent / node_power_source via pmset

Pairs with home-lab PR #29 (the Prometheus/Grafana containers).
@GregHilston GregHilston merged commit 161aef0 into master Jun 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant