mirror of https://github.com/garrytan/gstack.git
`gstack-slug` is an extension-less bash script. On Windows, spawnSync
without a shell goes through CreateProcess, which does not honour shebangs:
the call fails with ENOENT, stdout is null, the SLUG regex does not match,
and resolveSlug returns the literal "unknown".
Repro (Windows, git-bash, bun):
bun -e 'const {spawnSync}=require("child_process");
const r=spawnSync("C:/Users/<u>/.claude/skills/gstack/bin/gstack-slug",
{encoding:"utf-8"});
console.log(r.error&&r.error.code, JSON.stringify(r.stdout))'
# ENOENT null
The impact is worse than a wrong path. Both consumers — gstack-decision-log
and gstack-decision-search — then read and write ~/.gstack/projects/unknown/.
On the write side that is a single anonymous bucket shared by every repo on
the machine. On the read side the directory does not exist, so the search
returns an empty list and exits 0: the session is told there are no prior
decisions and re-litigates settled calls in good faith. The `?? "unknown"`
fallback turned an exec failure into a plausible value, which is why this
went unnoticed for so long.
`shell: true` does not fix it — cmd.exe has no association for an
extension-less file and yields "unknown" too. Naming the interpreter does.
Two changes:
- Retry via `bash <script>` when the direct call yields no slug. The direct
call is still attempted first, so POSIX behaviour is unchanged. The retry
is keyed on "no slug parsed" rather than on ENOENT specifically, so a shim
that exits non-zero without output is covered as well. bash and not sh:
gstack-slug uses `[[ ]]` and `set -o pipefail`.
- Warn on stderr before falling back. The fallback value is kept for
compatibility, but a tooling failure is not an anonymous project, and a
mute fallback is what allowed this to live.
Verified on Windows 11 / git-bash / bun 1.3.14:
- nominal: resolveSlug returns benjaminberes-bp-alfred-agent
- failure: warns `direct: ENOENT; via bash: exited 127` and still returns
"unknown", so no caller breaks
- end to end: `gstack-decision-search --recent 5` goes from empty to 4
records against an existing 3930-byte decisions.active.json
gitBranch() in the same file is unaffected: it spawns `git`, which Windows
resolves via PATHEXT. Only extension-less scripts break, so an audit should
look for spawnSync calls targeting bin/gstack-*.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| diagram-render | ||
| bin-context.ts | ||
| conductor-env-shim.ts | ||
| gbrain-exec.ts | ||
| gbrain-guards.ts | ||
| gbrain-local-status.ts | ||
| gbrain-sources.ts | ||
| gstack-decision-semantic.ts | ||
| gstack-decision.ts | ||
| gstack-memory-helpers.ts | ||
| is-conductor.ts | ||
| jsonl-store.ts | ||
| redact-audit-log.ts | ||
| redact-engine.ts | ||
| redact-patterns.ts | ||
| staging-guard.ts | ||
| worktree.ts | ||