Fix: resolveSlug silently returns "unknown" on Windows - #2367
Open
benjaminberes-bp wants to merge 2 commits into
Open
Fix: resolveSlug silently returns "unknown" on Windows#2367benjaminberes-bp wants to merge 2 commits into
benjaminberes-bp wants to merge 2 commits into
Conversation
`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>
Pins both halves of the bug: an extension-less shebang script must resolve on every platform, and a resolution failure must be audible. Verified to fail against the pre-fix function (both cases) and pass after it. The first case is the one that reproduces only on Windows; the second holds everywhere, which is what keeps the silent-fallback regression from returning on any platform. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
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.
On Windows,
resolveSlug()never resolves a slug. It returns the literal"unknown", with exit code 0 and no output.bin/gstack-slugis an extension-less bash script.spawnSyncwithout a shellgoes through
CreateProcess, which does not honour shebangs: the call failswith
ENOENT,stdoutisnull, theSLUG=regex does not match, and the: "unknown"branch is taken.Why this is worse than a wrong path
Both consumers,
bin/gstack-decision-logandbin/gstack-decision-search,then work against
~/.gstack/projects/unknown/.list and exits 0. The session reads "no prior decisions" and re-litigates
settled calls in good faith.
Observed on a real project:
gstack-decision-search --recent 5printed nothingwhile
~/.gstack/projects/<slug>/decisions.active.jsonheld 3930 bytes ofdecisions at the correct path. The reader was simply looking elsewhere.
The
: "unknown"fallback turned an exec failure into a plausible value, whichis why this went unnoticed rather than surfacing as an error.
The fix
shell: truedoes not work —cmd.exehas no association for anextension-less file and yields
"unknown"too. Naming the interpreter does.bash <script>when the direct call yields no slug. The direct callis still attempted first, so POSIX behaviour is unchanged. The retry is
keyed on "no slug parsed" rather than on
ENOENTspecifically, so a shim thatexits non-zero without output is covered as well.
bashand notsh:gstack-sluguses[[ ]]andset -o pipefail.breaks, but a tooling failure is not an anonymous project.
Tests
test/bin-context-resolve-slug.test.tspins both halves — that anextension-less shebang script resolves on every platform, and that a failure is
audible. Verified failing against the pre-fix function and passing after it.
Notes
gitBranch()in the same file is unaffected: it spawnsgit, which Windowsresolves via
PATHEXT. Only extension-less scripts break, so an audit couldlook for other
spawnSynccalls targetingbin/gstack-*.Verified on Windows 11 / git-bash / bun 1.3.14. Happy to adjust the approach if
you prefer a
process.platformcheck over the retry-on-empty.