Repository navigation
fix(bindings): write skills and commands under CLAUDE_CONFIG_DIR - #137
Conversation
The write path resolved $HOME/.claude directly while the read path (foreign.ConfigDir) honors CLAUDE_CONFIG_DIR, so a sync with the variable set wrote into the operator's real config. Route claudeCommandsDir and claudeSkillsDir through foreign.ConfigDir. A package TestMain clears CLAUDE_CONFIG_DIR so HOME-isolated tests cannot write into the config dir of the harness running them; without it the suite leaked 16 entries into a probe dir. Refs: #136 Refs: aae-orc-c07dl
|
Handoff (token exhaustion, 2026-09-28): |
arcavenai
left a comment
There was a problem hiding this comment.
Reviewed at 4322058. The signature is valid.
-
Red/green. I copied the new test files onto main (0f862ea).
TestSync_HonorsClaudeConfigDirthere: red, 1 fail, 0 pass, and all three assertions fire (skill missing from the config dir, command missing,$HOME/.claudecreated). On the branch: 1 pass. -
End to end. I built both binaries and ran
installthencommands syncagainst a two-artifact pack, withHOME,CLAUDE_CONFIG_DIRandSIDESHOW_HOMEall isolated. Main wrote 2 files under$HOME/.claudeand 0 under the config dir. The branch wrote 0 and 2. This matches the body. -
Test guard. I ran the full suite with
CLAUDE_CONFIG_DIRpointed at an empty probe dir, which is the situation of a harness that sets it:- with the new
TestMain: 0 entries leaked, 15 packages ok; - with
TestMainremoved as a control: 18 entries leaked.
The guard is doing real work. It also covers the other packages that reach the bindings (
enable,adopt,doctor,pack,coexistcheck): nothing leaks through them. - with the new
-
Gate.
just cipasses locally (golangci-lint 0 issues). The PR's checks are green. -
Scope and redaction. The change is exactly the two resolvers. Every write site in
bindings(markdown_command.go,skill_dir.go,custom_skill_dir.go, and the reconcile inmanifest.go) goes through them, so sync and reconcile move together. I scanned the diff, the commit message and the PR text for org, product and host tokens, with a positive control (control 1 hit, scan 0 hits). No em dashes and no attribution.
Merge recommended.
Notes for the follow-up, not blocking:
-
The permissions split is wider than the body's gap note says.
permissions.SettingsPath(internal/permissions/permissions.go:33-34,ScopeUser) still resolves$HOME/.claude/settings.json. The gap note describes only theSIDESHOW_HOMEcase, where the write is skipped. The ordinary case is different:CLAUDE_CONFIG_DIRset andSIDESHOW_HOMEunset. There,installstill addsRead(<store>/packs/)to$HOME/.claude/settings.json, while the bindings now land in the config dir. I reproduced this with the branch binary. So a harness that reads the config dir gets the skills but not the Read grant for the pack store.This is not a regression. On main, that user got neither, in a file the harness does not read. But after this merge the two halves disagree where before they were consistently wrong. The fix is the same one-liner:
ScopeUserreturnsfilepath.Join(foreign.ConfigDir(), "settings.json"), with a test like this one. The same run confirms that--scope userwithSIDESHOW_HOMEset writes the$HOMEfile. -
Ticket framing. aae-orc-c07dl's title and "Expected" line name
SIDESHOW_HOME. With onlySIDESHOW_HOMEset, bindings still go to$HOME/.claudeon this branch. I think that is right:SIDESHOW_HOMEis the store, and the harness config belongs toCLAUDE_CONFIG_DIR, where the harness actually reads. The ticket allows "or CLAUDE_CONFIG_DIR", so this change meets its binding half. Isolation needsCLAUDE_CONFIG_DIR, and that is not documented anywhere inREADME.mdordocs/. A line saying so would close the "docs present SIDESHOW_HOME as isolating" branch of the ticket.
With
CLAUDE_CONFIG_DIRset, a sync still wrote skills and commands into$HOME/.claude, so a scratch or test install could rewrite the operator's live bindings. Now the write path resolves the config dir the same way the read path already did.Change:
claudeCommandsDirandclaudeSkillsDirroute throughforeign.ConfigDir():CLAUDE_CONFIG_DIRwhen it is set,~/.claudeotherwise. No new configuration surface. A packageTestMainclearsCLAUDE_CONFIG_DIRso that tests which isolate by settingHOMEcan't write into the config dir of whatever harness runs them.Acceptance:
TestSync_HonorsClaudeConfigDirfailed on main with all three assertions (skill missing from the config dir, command missing,$HOME/.claudecreated). It passes on this branch.HOME+CLAUDE_CONFIG_DIR+SIDESHOW_HOME,installthencommands sync: the main binary wrote 2 files under$HOME/.claudeand 0 under the config dir. This branch wrote 0 and 2.CLAUDE_CONFIG_DIRpointing at a probe dir leaked 16 entries withoutTestMainand 0 with it. The full suite leaks 0.just ci: fmt, golangci-lint (0 issues), vet, and test all pass.Blast radius: this only changes behavior when
CLAUDE_CONFIG_DIRis set. For such a user, bindings synced by an earlier version stay under$HOME/.claude; later syncs write, and reconcile, under the config dir. The old copies stay where they are and are not removed automatically.sideshow adopt --migrate-user-scopealso resolves throughforeign.ConfigDir(), so it reaches them only when run withCLAUDE_CONFIG_DIRunset. With the variable unset, the paths are byte-identical to before.Opportunities (not touched here): the user-scope permissions writer (
internal/permissions,ScopeUser) still resolves~/.claude/settings.jsondirectly. WhenSIDESHOW_HOMEis set it is skipped, not redirected. That is the same shape as this bug and is left for its own change.Refs: #136
Refs: aae-orc-c07dl