ship: redact pre-push guard never auto-installs on Windows (HOOKS_IN_GIT_DIR path-format mismatch)
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Summary
On Windows (Git Bash / MSYS), the `HOOKS_IN_GIT_DIR` probe in `/ship` Step "redact pre-push guard" always evaluates to `no`. The guard is therefore never installed silently, even when the user has explicitly opted in with `gstack-config set redact_prepush_hook true`.
The user sets the flag, sees it read back as `true`, and reasonably believes the credential guard is active. It is not. It fails closed on install, which is the safe direction, but silently, so the opt-in is inert.
## Environment
- gstack v1.79.0.0
- Windows 11, Git Bash (MSYS2)
- git 2.x, repo using linked worktrees
## Root cause
`ship/SKILL.md.tmpl` lines 432 to 442 build two path strings through different mechanisms and then string-match them:
```bash
_HOOKS_DIR=$(git rev-parse --git-path hooks ...) # line 432
_GIT_COMMON=$(cd "$(git rev-parse --git-common-dir ...)" && pwd ...) # line 439
case "$_HOOKS_DIR" in
"$_GIT_DIR"/*|"$_GIT_COMMON"/*|hooks|.git/hooks) _HOOKS_IN_GIT_DIR="yes" ;;
esac
```
On Windows these three commands return three different path formats for the same directory:
| Command | Output |
|---|---|
| `git rev-parse --git-path hooks` | `C:\Users\me\proj\.git\hooks` |
| `git rev-parse --git-common-dir` | `C:/Users/me/proj/.git` |
| `cd "$(...)" && pwd` | `/c/Users/me/proj/.git` |
`_GIT_COMMON` ends up in MSYS form (`/c/...`) because `pwd` in Git Bash normalizes to it. `_HOOKS_DIR` stays in Windows form. The glob `/c/Users/me/proj/.git/*` can never match `C:\Users\me\proj\.git\hooks`, so the case falls through to `no`.
Note this is not limited to repos that set `core.hooksPath`. Any absolute value returned by `--git-path hooks`, which includes the linked-worktree case the `_GIT_COMMON` fallback was specifically added to handle, hits the same mismatch on Windows. The `hooks` and `.git/hooks` literal patterns only match the relative form, so plain non-worktree repos on Windows happen to pass.
## Reproduction
In a Git Bash shell on Windows, inside a linked worktree or any repo with an absolute `core.hooksPath`:
```bash
git rev-parse --git-path hooks
git rev-parse --git-common-dir
cd "$(git rev-parse --git-common-dir)" && pwd
```
Observed on my machine:
```
C:\Users\nblin\Projects\foresight-aero-website\.git\hooks
C:/Users/nblin/Projects/foresight-aero-website/.git
/c/Users/nblin/Projects/foresight-aero-website/.git
```
Running the Step block verbatim then prints:
```
REDACT_PREPUSH: true
HOOK_INSTALLED: no
HOOKS_IN_GIT_DIR: no
```
so `/ship` takes the "custom core.hooksPath" branch and skips the install.
## Impact
Opt-in users on Windows silently get no pre-push credential guard. `gstack-redact install-prepush-hook` itself works correctly when run by hand, and the installed hook functions: feeding it a synthetic `sk_live_...` key returns exit 3 as documented. Only the automatic install path is affected.
## Suggested fix
Normalize both sides through the same mechanism before comparing, rather than comparing raw `git rev-parse` output:
```bash
_HOOKS_DIR_RESOLVED=$(cd "$_HOOKS_DIR" 2>/dev/null && pwd || echo /nonexistent)
```
and match `$_HOOKS_DIR_RESOLVED` against `$_GIT_COMMON` and `$_GIT_DIR`. Since `cd` plus `pwd` yields MSYS form on Windows and POSIX form elsewhere, both sides then agree on all platforms.
One caveat: the hooks directory may not exist yet on a fresh clone, in which case `cd` fails. Falling back to normalizing the parent directory and appending the basename handles that.
An alternative is `git rev-parse --path-format=absolute --git-path hooks` (git 2.31+), which at least makes the git-side output format consistent, though it still returns `C:/...` rather than `/c/...` on Windows and so would need the same normalization on the other side.
Happy to send a PR if the maintainers prefer a particular approach.
🤖 Filed with [Claude Code](https://claude.com/claude-code)
Contributor guide
Research direction
Start in ship/SKILL.md.tmpl at the “redact pre-push guard” step, especially lines 432–442, and reproduce the path outputs in a Windows Git Bash linked worktree. Normalize the hook and Git directory paths consistently, including when the hooks directory does not yet exist. Done means the step reports HOOKS_IN_GIT_DIR: yes and automatically installs the opted-in guard in the affected Windows cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, git
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100