fix(host-paths): use $HOME instead of ~ in non-env-var host paths#1451
Open
jisulee42 wants to merge 1 commit into
Open
fix(host-paths): use $HOME instead of ~ in non-env-var host paths#1451jisulee42 wants to merge 1 commit into
~ in non-env-var host paths#1451jisulee42 wants to merge 1 commit into
Conversation
`HOST_PATHS` for Claude (and any future host with `usesEnvVars=false`) was
built from `~/${config.globalRoot}`. The resulting binDir is then
interpolated into double-quoted bash strings by several preamble emitters,
e.g. `generate-brain-sync-block.ts:45`:
_BRAIN_SYNC_BIN="${ctx.paths.binDir}/gstack-brain-sync"
POSIX shells do NOT expand `~` inside double quotes. The emitted line
becomes a literal `"~/.claude/.../gstack-brain-sync"`, which fails with
`exit 127, No such file or directory` when invoked. Both stderr and the
exit code are suppressed by `2>/dev/null || true`, so the failure is
silent: skill preambles call `gstack-brain-sync --once` on every skill
boundary, but it never runs. The artifacts queue (~/.gstack/.brain-queue.jsonl)
grows indefinitely while last-push stays at "never". Codex/Factory hosts
are unaffected because they use `$GSTACK_BIN`, which DOES expand inside
double quotes.
Fix: emit `$HOME/${config.globalRoot}` instead. Identical semantics outside
quotes, correct semantics inside quotes.
Why CI didn't catch it:
- `test/host-config.test.ts:246` asserted the broken form as the
expected value (`expect(...).toBe('~/.claude/skills/gstack/bin')`).
- The Claude golden fixture (`test/fixtures/golden/claude-ship-SKILL.md`)
was generated from the broken code path, so the regression suite was
comparing the bug against itself.
Changes:
- `scripts/resolvers/types.ts`: emit `$HOME/...` for non-env-var hosts
(one-line change + WHY comment).
- `test/host-config.test.ts`: update existing assertions to expect
`$HOME/...`. Add a narrow regression guard that asserts no non-env-var
host's binDir/skillRoot/browseDir/designDir/makePdfDir begins with `~`
(i.e., is safe to interpolate inside double quotes).
- Regenerated artifacts via `bun run gen:skill-docs --host all`:
42 Claude-host SKILL.md files + the claude-ship golden fixture flip
from `~/...` to `$HOME/...`. Codex/Factory artifacts unchanged.
Test plan:
- `bun test test/host-config.test.ts` — 74 pass, 0 fail (includes new guard).
- `bun test test/gen-skill-docs.test.ts test/brain-sync.test.ts` —
412 pass, 0 fail.
- `bun run test` (main suite, e2e excluded) — green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
HOST_PATHSfor Claude (and any future non-env-var host) emits pathsstarting with a literal
~. Several preamble blocks then interpolatethose paths inside double quotes (e.g.
generate-brain-sync-block.ts:45emits
_BRAIN_SYNC_BIN="${ctx.paths.binDir}/gstack-brain-sync").POSIX shells do not expand
~inside double quotes, so the emittedline is a literal
"~/.claude/.../gstack-brain-sync"and every call tothat binary fails with
exit 127, No such file or directory. Bothstderr and the exit code are swallowed by
2>/dev/null || true, sothe failure is silent.
Practical impact: on every Claude Code install with
artifacts_sync_modeon, the skill preambles call
gstack-brain-sync --onceon every skillboundary, but the binary is never reached. The artifacts queue
(
~/.gstack/.brain-queue.jsonl) grows indefinitely while~/.gstack/.brain-last-pushstays atnever. I hit this exact symptomlocally after running
/setup-gbrain— ~24 hours of skill usage, queueat 45 entries, last-push
never, status file never created. Manualgstack-brain-sync --oncedrained the queue and pushed cleanly, whichconfirmed the binary itself works and the problem was upstream of the
call.
codexandfactoryhosts are unaffected: they use$GSTACK_BIN,which DOES expand inside double quotes.
Root cause
scripts/resolvers/types.ts:37(before):The emitter at
scripts/resolvers/preamble/generate-brain-sync-block.ts:45then wraps that value in double quotes, defeating tilde expansion.
Fix
One-line: emit
$HOME/${config.globalRoot}instead of~/${config.globalRoot}.Same semantics outside quotes (both expand), correct semantics inside
quotes (only
$HOMEexpands). Added a WHY comment pointing at bash(1)"Tilde Expansion" + "Parameter Expansion" so the next person who looks
at this knows why it matters.
Why CI didn't catch it
Two reasons, both worth fixing:
test/host-config.test.ts:246asserted the broken form as theexpected value:
The test was passing because the code produced the same broken value
the test was asserting. Updated to expect
$HOME/....test/fixtures/golden/claude-ship-SKILL.mdwas generated fromthe broken code path, so golden-file regression was comparing the
bug against itself. Regenerated via
bun run gen:skill-docs --host all.Also added a narrow regression guard at
test/host-config.test.ts(HOST_PATHS describe block):If anyone reintroduces the literal-tilde form later, this fires
immediately.
Diff shape
scripts/resolvers/types.ts— +6 / -1 (one-line fix + WHY comment).test/host-config.test.ts— +20 / -8 (assertion updates + regressionguard).
test/fixtures/golden/claude-ship-SKILL.md— +57 / -57 (goldenregen,
~→$HOME).*/SKILL.mdfiles — auto-regenerated viabun run gen:skill-docs --host all. Codex/Factory artifactsunchanged.
The 42 SKILL.md files dominate the diff line count but are mechanical
regen output. Reviewer focus:
scripts/resolvers/types.ts(6 lines)and
test/host-config.test.ts(~30 lines).Test plan
bun test test/host-config.test.ts— 74 pass, 0 fail (includesnew regression guard).
bun test test/gen-skill-docs.test.ts test/brain-sync.test.ts—412 pass, 0 fail.
bun test test/preamble-compose.test.ts test/gstack-gbrain-sync.test.ts test/gbrain-sources.test.ts—34 pass, 0 fail.
bun run test(main suite, e2e excluded) — green locally.gstack-brain-sync --oncenow reachable, queue drains, push succeeds,
~/.gstack/.brain-last-pushupdates.
Notes for maintainer
I found this while debugging "/sync-gbrain isn't actually pushing
anything" on a fresh
/setup-gbraininstall. Happy to split intotwo PRs (fix vs. regenerated artifacts) if you'd prefer the diff
to land more narrowly, but the regen is verbatim output of
bun run gen:skill-docs --host allagainst the one-line fix andshould be deterministic. Searched open issues/PRs for prior reports
of this — none found.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need.