Skip to content

fix: stop apply pin escaping to $HOME via the global ~/.claude#19

Merged
vessux merged 1 commit into
mainfrom
fix/apply-pin-home-escape
Jun 10, 2026
Merged

fix: stop apply pin escaping to $HOME via the global ~/.claude#19
vessux merged 1 commit into
mainfrom
fix/apply-pin-home-escape

Conversation

@vessux

@vessux vessux commented Jun 10, 2026

Copy link
Copy Markdown
Owner

What & why

umbel apply <bundle> wrote .umbel-bundle to $HOME instead of the repo root when the repo had no .claude/ directory — silently routing every home project's plain claude (via the PATH shim) through one bundle. Closes #14.

Root cause

findClaudeAncestor (src/target/walk.ts) checked dir/.claude before the dir === home boundary. ~/.claude is the global Claude config dir — present on every machine that has run Claude, never a project marker. On a fresh clone (the repo's .claude/ is gitignored, hence absent), the pin walk (stopAtGit: false) climbed past the repo, reached $HOME, matched ~/.claude, and returned $HOME → pin written at $HOME/.umbel-bundle. The function's own doc already promised "returns null … before reaching home" — the code violated its own contract.

Fix

One reorder: check dir === home before the .claude match, making home an exclusive upper boundary. pinPath then falls back to cwd (the repo root in the reported reproduction).

Fixed at the shared findClaudeAncestor rather than patched at the pin layer, so the same latent $HOME-escape is also closed for skills/bundle discovery (non-git dirs under $HOME). Blast radius across all four consumers (findProjectRoot, findClaudeSkillsDir, findClaudeBundlesDir, bundle-init) verified benign — the only behavior shift is no longer treating the global ~/.claude as a project root, which is exactly what the issue asks for. Applying at $HOME (cwd === home) still writes a home pin via the cwd fallback; only upward escape from a subdir is prevented.

Tests (TDD — failed first, then fixed)

  • pin.test.ts: pin anchors at the repo (not $HOME) when the repo lacks .claude/; findProjectRoot stops at the home boundary; cwd === home falls back to cwd.
  • target.walk.test.ts: home is an exclusive boundary even when ~/.claude exists.

Verification

  • npm test → 341 passing
  • tsc --noEmit clean · biome check . clean
  • Fresh-eyes code review: ready to merge, no Critical/Important issues.

findClaudeAncestor checked dir/.claude before the dir===home boundary, so on
a fresh clone (repo .claude/ is gitignored, hence absent) the pin walk
(stopAtGit:false) climbed to $HOME, matched the global ~/.claude config dir,
and wrote .umbel-bundle at $HOME — silently routing every home project's
plain claude through one bundle.

Treat home as an exclusive upper boundary (checked before the .claude match),
matching the function's own documented contract. pinPath then falls back to
cwd (the repo root). Also closes the same latent escape in skills/bundle
discovery for non-git dirs under $HOME.

Closes #14
@vessux vessux merged commit 8b66602 into main Jun 10, 2026
4 checks passed
@vessux vessux deleted the fix/apply-pin-home-escape branch June 10, 2026 16:53
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.

umbel apply pins .umbel-bundle at $HOME when the repo lacks .claude/

1 participant