From 8dd54779f722d91f69ac3b62f5c0243d532ba559 Mon Sep 17 00:00:00 2001 From: Simonas <20096648+simjak@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:44:00 +0300 Subject: [PATCH] fix: stop env-var hosts from doubling $HOME in binary-resolver fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Non-Claude hosts (codex, factory, cursor, opencode, slate, kiro, hermes, gbrain) resolve the browse/design/make-pdf binaries through absolute $GSTACK_* env vars. The setup blocks built the global-install fallback as `$HOME${dir.replace(/^~/, '')}/bin`, which assumes a ~-rooted dir. For env-var hosts `dir` is already `$GSTACK_BROWSE` (absolute, contains $HOME), so $HOME got prepended twice — e.g. `$HOME$GSTACK_BROWSE/browse` expands to /home/me/home/me/.codex/.../browse and never resolves, leaving the skill stuck on BROWSE_NOT_AVAILABLE / NEEDS_SETUP even when the binary is built. Add toShellPath() in resolvers/types.ts: ~-rooted dirs expand via $HOME, absolute env-var dirs pass through untouched. Applied at all six call sites in browse.ts, design.ts, make-pdf.ts. Claude output is byte-identical; env-var hosts now emit $GSTACK_BROWSE/browse. tsc clean; resolver + make-pdf + golden host-config suites pass (270). --- scripts/resolvers/browse.ts | 4 ++-- scripts/resolvers/design.ts | 8 ++++---- scripts/resolvers/make-pdf.ts | 4 ++-- scripts/resolvers/types.ts | 10 ++++++++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/scripts/resolvers/browse.ts b/scripts/resolvers/browse.ts index a0ae37a70..487c40f78 100644 --- a/scripts/resolvers/browse.ts +++ b/scripts/resolvers/browse.ts @@ -1,4 +1,4 @@ -import type { TemplateContext } from './types'; +import { type TemplateContext, toShellPath } from './types'; import { COMMAND_DESCRIPTIONS } from '../../browse/src/commands'; import { SNAPSHOT_FLAGS } from '../../browse/src/snapshot'; @@ -106,7 +106,7 @@ export function generateBrowseSetup(ctx: TemplateContext): string { _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) B="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" -[ -z "$B" ] && B="$HOME${ctx.paths.browseDir.replace(/^~/, '')}/browse" +[ -z "$B" ] && B="${toShellPath(ctx.paths.browseDir)}/browse" if [ -x "$B" ]; then echo "READY: $B" else diff --git a/scripts/resolvers/design.ts b/scripts/resolvers/design.ts index 9f31b3619..a6200145e 100644 --- a/scripts/resolvers/design.ts +++ b/scripts/resolvers/design.ts @@ -1,4 +1,4 @@ -import type { TemplateContext } from './types'; +import { type TemplateContext, toShellPath } from './types'; import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS } from './constants'; export function generateDesignReviewLite(ctx: TemplateContext): string { @@ -792,7 +792,7 @@ export function generateDesignSetup(ctx: TemplateContext): string { _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) D="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" -[ -z "$D" ] && D="$HOME${ctx.paths.designDir.replace(/^~/, '')}/design" +[ -z "$D" ] && D="${toShellPath(ctx.paths.designDir)}/design" if [ -x "$D" ]; then echo "DESIGN_READY: $D" else @@ -800,7 +800,7 @@ else fi B="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" -[ -z "$B" ] && B="$HOME${ctx.paths.browseDir.replace(/^~/, '')}/browse" +[ -z "$B" ] && B="${toShellPath(ctx.paths.browseDir)}/browse" if [ -x "$B" ]; then echo "BROWSE_READY: $B" else @@ -837,7 +837,7 @@ export function generateDesignMockup(ctx: TemplateContext): string { _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) D="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" -[ -z "$D" ] && D="$HOME${ctx.paths.designDir.replace(/^~/, '')}/design" +[ -z "$D" ] && D="${toShellPath(ctx.paths.designDir)}/design" [ -x "$D" ] && echo "DESIGN_READY" || echo "DESIGN_NOT_AVAILABLE" \`\`\` diff --git a/scripts/resolvers/make-pdf.ts b/scripts/resolvers/make-pdf.ts index c73d0bf13..22ab38930 100644 --- a/scripts/resolvers/make-pdf.ts +++ b/scripts/resolvers/make-pdf.ts @@ -1,4 +1,4 @@ -import type { TemplateContext } from './types'; +import { type TemplateContext, toShellPath } from './types'; /** * {{MAKE_PDF_SETUP}} — emits the shell preamble that resolves $P to the @@ -19,7 +19,7 @@ _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) P="" [ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN" [ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/make-pdf/dist/pdf" ] && P="$_ROOT/${ctx.paths.localSkillRoot}/make-pdf/dist/pdf" -[ -z "$P" ] && P="$HOME${ctx.paths.makePdfDir.replace(/^~/, '')}/pdf" +[ -z "$P" ] && P="${toShellPath(ctx.paths.makePdfDir)}/pdf" if [ -x "$P" ]; then echo "MAKE_PDF_READY: $P" alias _p_="$P" # shellcheck alias helper (not exported) diff --git a/scripts/resolvers/types.ts b/scripts/resolvers/types.ts index 40afed160..68514dffd 100644 --- a/scripts/resolvers/types.ts +++ b/scripts/resolvers/types.ts @@ -50,6 +50,16 @@ function buildHostPaths(): Record { export const HOST_PATHS: Record = buildHostPaths(); +/** + * Render a HostPaths binary dir as a shell-expandable absolute path. + * Claude-style dirs are `~`-rooted (e.g. `~/.claude/skills/gstack/browse/dist`) + * and expand via `$HOME`; env-var hosts already carry an absolute `$GSTACK_*` + * value, so they pass through untouched — prepending `$HOME` would double it. + */ +export function toShellPath(dir: string): string { + return dir.startsWith('~') ? `$HOME${dir.slice(1)}` : dir; +} + import type { Model } from '../models'; export type { Model } from '../models';