fix: use absolute paths for gsd-tools.cjs in all install types (#820) (#1062)

Local installs wrote $HOME/.claude/get-shit-done/bin/gsd-tools.cjs into
workflow files, which breaks when GSD is installed outside $HOME (e.g.
external drives, symlinked projects) and when spawned subagents have an
empty $HOME environment variable.

- pathPrefix now always resolves to an absolute path via path.resolve()
- All $HOME/.claude/ replacements use the absolute prefix directly
- Codex installer uses absolute path for get-shit-done prefix
- Removed unused toHomePrefix() function

Tested: 535/535 existing tests pass, verified local install produces
correct absolute paths, verified global install unchanged, verified
empty $HOME scenario resolves correctly.

Closes #820

Made-with: Cursor

Co-authored-by: ralberts3 <ralberts3@gatech.edu>
This commit is contained in:
TÂCHES
2026-03-15 11:44:25 -06:00
committed by GitHub
parent 386fc0f40c
commit 5d703954c9
2 changed files with 15 additions and 36 deletions

View File

@@ -8,6 +8,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
- OpenCode agent frontmatter conversion — agents now get correct `name:`, `model: inherit`, `mode: subagent` instead of broken command-style conversion that stripped name and kept `color:`/`skills:`/`tools:`
- Local install: workflow and reference files now get absolute path to `gsd-tools.cjs` instead of `$HOME/.claude/get-shit-done/`, so local installs work when GSD is outside `$HOME` and spawned subagents with empty `$HOME` still resolve the path (#820)
## [1.23.0] - 2026-03-15

View File

@@ -115,25 +115,6 @@ Then re-run: npx get-shit-done-cc@latest
}
}
/**
* Convert a pathPrefix (which uses absolute paths for global installs) to a
* $HOME-relative form for replacing $HOME/.claude/ references in bash code blocks.
* Preserves $HOME as a shell variable so paths remain portable across machines.
*/
function toHomePrefix(pathPrefix) {
const home = os.homedir().replace(/\\/g, '/');
const normalized = pathPrefix.replace(/\\/g, '/');
if (normalized.startsWith(home)) {
return '$HOME' + normalized.slice(home.length);
}
// Convert tilde-based paths to $HOME-based paths for bash code blocks
if (normalized.startsWith('~/')) {
return '$HOME' + normalized.slice(1);
}
// For relative paths or paths not under $HOME, return as-is
return normalized;
}
// Helper to get directory name for a runtime (used for local/project installs)
function getDirName(runtime) {
if (runtime === 'copilot') return '.github';
@@ -941,15 +922,14 @@ function installCodexConfig(targetDir, agentsSrc) {
const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.startsWith('gsd-') && f.endsWith('.md'));
const agents = [];
// Compute the Codex pathPrefix for replacing .claude paths
// Use tilde-based path to avoid baking absolute paths into templates
const codexPathPrefix = `${targetDir.replace(/\\/g, '/').replace(os.homedir().replace(/\\/g, '/'), '~')}/`;
// Compute the Codex GSD install path (absolute, so subagents with empty $HOME work — #820)
const codexGsdPath = `${path.resolve(targetDir, 'get-shit-done').replace(/\\/g, '/')}/`;
for (const file of agentEntries) {
let content = fs.readFileSync(path.join(agentsSrc, file), 'utf8');
// Replace .claude paths before generating TOML (source files use ~/.claude and $HOME/.claude)
content = content.replace(/~\/\.claude\//g, codexPathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, toHomePrefix(codexPathPrefix));
// Replace full .claude/get-shit-done prefix so path resolves to codex GSD install
content = content.replace(/~\/\.claude\/get-shit-done\//g, codexGsdPath);
content = content.replace(/\$HOME\/\.claude\/get-shit-done\//g, codexGsdPath);
const { frontmatter } = extractFrontmatterAndBody(content);
const name = extractFrontmatterField(frontmatter, 'name') || file.replace('.md', '');
const description = extractFrontmatterField(frontmatter, 'description') || '';
@@ -1306,7 +1286,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
const localClaudeRegex = /\.\/\.claude\//g;
const opencodeDirRegex = /~\/\.opencode\//g;
content = content.replace(globalClaudeRegex, pathPrefix);
content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
content = content.replace(globalClaudeHomeRegex, pathPrefix);
content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
content = content.replace(opencodeDirRegex, pathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
@@ -1367,7 +1347,7 @@ function copyCommandsAsCodexSkills(srcDir, skillsDir, prefix, pathPrefix, runtim
const localClaudeRegex = /\.\/\.claude\//g;
const codexDirRegex = /~\/\.codex\//g;
content = content.replace(globalClaudeRegex, pathPrefix);
content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
content = content.replace(globalClaudeHomeRegex, pathPrefix);
content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
content = content.replace(codexDirRegex, pathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
@@ -1466,7 +1446,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
const localClaudeRegex = /\.\/\.claude\//g;
content = content.replace(globalClaudeRegex, pathPrefix);
content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
content = content.replace(globalClaudeHomeRegex, pathPrefix);
content = content.replace(localClaudeRegex, `./${dirName}/`);
}
content = processAttribution(content, getCommitAttribution(runtime));
@@ -2259,13 +2239,11 @@ function install(isGlobal, runtime = 'claude') {
? targetDir.replace(os.homedir(), '~')
: targetDir.replace(process.cwd(), '.');
// Path prefix for file references in markdown content
// For global installs: use tilde-based path (~/.claude/) to avoid baking
// absolute paths (containing OS username) into templates
// For local installs: use relative
const pathPrefix = isGlobal
? `${targetDir.replace(/\\/g, '/').replace(os.homedir().replace(/\\/g, '/'), '~')}/`
: `./${dirName}/`;
// Path prefix for file references in markdown content (e.g. gsd-tools.cjs).
// Replaces $HOME/.claude/ or ~/.claude/ so the result is <pathPrefix>get-shit-done/bin/...
// Always use absolute path so: (1) local installs work when GSD is outside $HOME,
// (2) spawned subagents with empty $HOME still resolve the path (fixes #820).
const pathPrefix = `${path.resolve(targetDir).replace(/\\/g, '/')}/`;
let runtimeLabel = 'Claude Code';
if (isOpencode) runtimeLabel = 'OpenCode';
@@ -2374,7 +2352,7 @@ function install(isGlobal, runtime = 'claude') {
const homeDirRegex = /\$HOME\/\.claude\//g;
if (!isCopilot) {
content = content.replace(dirRegex, pathPrefix);
content = content.replace(homeDirRegex, toHomePrefix(pathPrefix));
content = content.replace(homeDirRegex, pathPrefix);
}
content = processAttribution(content, getCommitAttribution(runtime));
// Convert frontmatter for runtime compatibility (agents need different handling)