Description
runPostCommitHook() only clears the pending hook entry on successful processing, invalid JSON, an empty commit message, or an appendHistoryEntry() failure. If an earlier operation in the outer try throws — especially checkGitRepo() or readLatestCommitMessage() — the catch block only emits a warning and leaves the pending file behind.
That stale entry can then be consumed by a later successful commit. The later commit's message is paired with the older commit's diff/model/provider metadata.
Location
src/git/hook.ts:220-253 — runPostCommitHook()
Relevant code
try {
deps.checkGitRepo();
...
const message = deps.readLatestCommitMessage().trim();
...
try {
await deps.appendHistoryEntry(entry);
} finally {
await deps.removePendingEntryFile();
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
deps.warn(`commit-echo hook: ${message}`);
}
The outer catch has no cleanup.
Steps to reproduce
- Let
prepare-commit-msg create a valid pending entry for a commit.
- Make the following
post-commit invocation fail before the inner appendHistoryEntry() block (for example, make readLatestCommitMessage() throw in a test).
- Confirm the pending entry remains.
- Run another commit whose post-commit hook can read the latest commit message.
- The old pending entry is now used for the new commit.
A focused regression test can inject the existing PostCommitHookDeps callbacks to reproduce this without filesystem corruption.
Expected behavior
Any terminal error in the post-commit hook should leave no stale pending state that can be attributed to a future commit.
Actual behavior
Some outer-hook failures only warn and return, leaving commit-echo-pending-entry.json behind.
Suggested fix
Make pending-state ownership explicit and guarantee cleanup for the current pending record on all terminal paths. Ideally, key pending state by worktree/commit generation so a later hook invocation cannot consume an earlier invocation's metadata.
Impact
History can become silently incorrect: a later commit may be recorded with the previous commit's diff, model, and provider information. This undermines the learned style data without producing a hard failure.
Reviewed against current main at c67ff967a018d5fd4b032f6ef6a4981ac9d76a12.
Description
runPostCommitHook()only clears the pending hook entry on successful processing, invalid JSON, an empty commit message, or anappendHistoryEntry()failure. If an earlier operation in the outertrythrows — especiallycheckGitRepo()orreadLatestCommitMessage()— the catch block only emits a warning and leaves the pending file behind.That stale entry can then be consumed by a later successful commit. The later commit's message is paired with the older commit's diff/model/provider metadata.
Location
src/git/hook.ts:220-253—runPostCommitHook()Relevant code
The outer catch has no cleanup.
Steps to reproduce
prepare-commit-msgcreate a valid pending entry for a commit.post-commitinvocation fail before the innerappendHistoryEntry()block (for example, makereadLatestCommitMessage()throw in a test).A focused regression test can inject the existing
PostCommitHookDepscallbacks to reproduce this without filesystem corruption.Expected behavior
Any terminal error in the post-commit hook should leave no stale pending state that can be attributed to a future commit.
Actual behavior
Some outer-hook failures only warn and return, leaving
commit-echo-pending-entry.jsonbehind.Suggested fix
Make pending-state ownership explicit and guarantee cleanup for the current pending record on all terminal paths. Ideally, key pending state by worktree/commit generation so a later hook invocation cannot consume an earlier invocation's metadata.
Impact
History can become silently incorrect: a later commit may be recorded with the previous commit's diff, model, and provider information. This undermines the learned style data without producing a hard failure.
Reviewed against current
mainatc67ff967a018d5fd4b032f6ef6a4981ac9d76a12.