fix(plan-tune): project-local question preferences never matched — hook used basename, writers use gstack-slug#2332
Open
jawadakram20 wants to merge 1 commit into
Conversation
…ok used basename, writers use gstack-slug
`slugFromCwd()` in the AskUserQuestion PreToolUse hook returned
`path.basename(cwd)`. Every writer of project-local state uses the slug from
`bin/gstack-slug`, which derives it from the git remote (owner-repo) and only
falls back to basename when the repo has NO remote configured:
plan-tune/SKILL.md: _PROJ="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG}"
hook: <stateRoot>/projects/<basename>/question-preferences.json
In any repo with a remote the two disagree, so the hook read a directory that
does not exist. Observed on a real machine:
gstack-slug → Shmulyitz-tristatelvs (what /plan-tune writes)
basename → tristatelvs (what the hook read)
~/.gstack/projects/ → Shmulyitz-tristatelvs, Shmulyitz-Tristate-pim,
Shmulyitz-tristate-vendure, millwoods-registry
Every bucket is remote-derived; no basename-named directory existed at all.
Impact: project-local preferences were silently inert, and the documented
project > global precedence (D8) collapsed to global-only. A user setting a
per-project never-ask preference got no enforcement and no error.
Fix: resolve the same slug gstack-slug does by reading its on-disk cache
(`<root>/slug-cache/<abs-path-with-slashes-as-underscores>`), falling back to
basename on a cache miss — which mirrors gstack-slug's own no-remote
fallback. This is a single file read, so the hot-path constraint that
motivated the basename shortcut still holds; we do not shell out to git. The
cached value is re-sanitized to the [a-zA-Z0-9._-] invariant on read because
it becomes a path segment.
Why it survived: every fixture in question-preference-hook.test.ts used a cwd
whose basename happened to equal the slug, so the mismatch could not appear.
The two added tests pin the real contract — the first FAILS on the old code
(expected "deny", received "defer"), the second covers the no-remote fallback.
Verified: 29 pass / 0 fail across question-preference-hook and
memory-cache-injection. Also confirmed end-to-end against a real slug cache.
|
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.
The bug
slugFromCwd()in the AskUserQuestion PreToolUse hook returnedpath.basename(cwd). Every writer of project-local state uses the slug frombin/gstack-slug, which derives it from the git remote (owner-repo) and falls back to basename only when the repo has no remote:In any repo with a remote the two disagree, so the hook read a directory that does not exist.
Observed on a real machine
Every bucket is remote-derived. No basename-named directory existed at all.
Impact
Project-local preferences were silently inert, and the documented project > global precedence (D8) collapsed to global-only. A user setting a per-project
never-askpreference got no enforcement and no error — the hook just deferred every time.Fix
Resolve the same slug
gstack-slugdoes by reading its on-disk cache (<root>/slug-cache/<abs-path-with-slashes-as-underscores>), falling back to basename on a miss — mirroring gstack-slug's own no-remote fallback.This is a single file read, so the hot-path constraint that motivated the basename shortcut still holds; we do not shell out to git. The cached value is re-sanitized to the
[a-zA-Z0-9._-]invariant on read, since it becomes a path segment.Why it survived
Every fixture in
question-preference-hook.test.tsused a cwd whose basename happened to equal the slug, so the mismatch could not appear.Two tests are added. The first fails on the old code — confirmed by stashing the fix:
The second covers the no-remote basename fallback.
Verification
Also confirmed end-to-end against a real slug cache: with a preference written only under
Shmulyitz-tristatelvs, the hook now resolves that bucket fromcwd=/Users/…/tristatelvsand enforces it.