pre-commit stash aborts the commit and orphans a stash when every stashed path matches HEAD (git update-index --chmod) #1281
SummaryThe pre-commit stash aborts the commit with whenever every path hk hands to The reliable way into that state is a mode-only staged change: git update-index --chmod=+x script.sh
git commit -m "make script executable"
It is not specific to mode changes: staging an edit and then restoring the file's worktree Environment
Reproduced on 1.53.0, 1.55.0 and 1.56.1. Mechanism
git implements the pathspec form by diffing HEAD against the worktree tree and So the criterion that puts a file into the pathspec list (index vs worktree) and the criterion Two details worth spelling out, because they shape what a fix can look like:
Damagegit creates the stash entry before the failing
The staged change is recoverable, but not where people look first: On a shared stash stack (multiple worktrees off one repository) this reads as if something Minimal reproVerified verbatim against hk 1.56.1 / git 2.55.0: #!/bin/sh
set -eu
git init -q t && cd t
git config user.email t@t.t && git config user.name t
cat >hk.pkl <<'PKL'
amends "package://github.com/jdx/hk/releases/download/v1.56.1/hk@1.56.1#/Config.pkl"
hooks {
["pre-commit"] {
fix = true
stage = true
stash = "git"
steps {
["noop"] { glob = List("**/*.sh"); check = "true" }
}
}
}
PKL
printf 'echo hi\n' >a.sh
git add -A && git commit -qm init
hk install
# index 100755, worktree still 100644 and byte-identical to HEAD
git update-index --chmod=+x a.sh
git commit -m "make a.sh executable"Aftermath: Same result with Variant matrixAll rows driven end to end through hk 1.56.1; all have
Row 2 is the one that makes this hard to work around by hand: the pathspec list is per file, so a Rows 5 and 7 together rule out the intuitive reading, that the trouble is an index mode and a What the orphaned stash containsWorth knowing before anyone tries to recover from it: the entry is not scoped to the pathspec. That content is still in the index (identical blob), so the entry reads as unique work when it is So the abort leaves behind an entry that is misleading in both directions: it advertises content Suggested fixBefore running the pathspec One residual is worth naming rather than hiding: for a path whose worktree matches HEAD while the |
Replies: 1 comment
|
Confirmed—your analysis of the empty HEAD-to-worktree patch and the resulting orphaned stash is correct. I opened #1283 with the aggregate The PR includes end-to-end regressions for both AI-assisted — Tool: Codex; model: openai/gpt-5; version: unavailable. |
Confirmed—your analysis of the empty HEAD-to-worktree patch and the resulting orphaned stash is correct. I opened #1283 with the aggregate
git diff --quiet HEAD -- <paths>preflight you suggested, so hk returns before invoking the destructive path-limited stash when there is no patch to apply.The PR includes end-to-end regressions for both
git update-index --chmodand a staged content change whose worktree was restored to HEAD, under both libgit2 settings. Thanks for the exceptionally thorough report and recovery notes.AI-assisted — Tool: Codex; model: openai/gpt-5; version: unavailable.