From dcdd28ce340d9972279540be34b74b2c35d5d9e3 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 14 Aug 2026 19:26:10 -0700 Subject: [PATCH] fix(resolvers): stop env-var hosts from doubling $HOME in the binary fallback path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The browse/design/make-pdf setup resolvers built the fallback binary path as "$HOME" + dir.replace(/^~/, ''), which is only correct for ~-rooted dirs. Env-var hosts carry an absolute $GSTACK_* dir, so the generated fallback became $HOME$GSTACK_.../browse — a path that never exists. New toShellPath() in scripts/resolvers/types.ts expands ~ to $HOME and passes absolute env-var dirs through untouched; all five call sites route through it. Claude-host generated output is byte-identical, so no SKILL.md regeneration is needed here. Closes #2055. Contributed by @simjak (PR #2056). Co-Authored-By: Claude Fable 5 --- 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 e56a0cf6c..7cb2949c8 100644 --- a/scripts/resolvers/types.ts +++ b/scripts/resolvers/types.ts @@ -71,6 +71,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';