`(& git log -1 --format='%b')` in PowerShell collapses multi-line subprocess
output into string[]. The threshold check used `$body.Length` which on a
string[] returns the line count, not char count — so the body>200 condition
was effectively comparing "more than 200 lines", which is much harder to
meet. Files-count saves it in practice for big commits, but small-file
big-message commits were under-detected.
Fix: join the array back into a single string with `-join "`n"` before
measuring length. Verified via stdin-pipe smoke against current HEAD:
body chars now report 1255 (vs 29 before — the line count).
POSIX `.sh` variant unaffected — `$()` collapses output and `${#var}` is
char count.
Bump 0.3.0 → 0.3.1 PATCH (bugfix, no behavior contract change).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
session-handoff hooks
Opt-in PostToolUse hook that detects substantive git commit invocations and signals the agent ("consider running session-handoff write-mode") via a system reminder. Replaces the agent-side commit-detection heuristic in the SKILL.md When to use section with a deterministic harness-side trigger.
Why opt-in (not auto-installed)
install.sh deliberately does not mutate ~/.claude/settings.json. Auto-rewriting the user's hook config on every skill install is the wrong shape — user expects install.sh to copy files, nothing more. The hook is shipped as scripts; user enables it once per machine.
Enable on Windows (PowerShell)
Add to ~/.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "pwsh -NoProfile -ExecutionPolicy Bypass -File \"$env:USERPROFILE\\.claude\\skills\\session-handoff\\hooks\\commit-detector.ps1\""
}
]
}
]
}
}
If hooks.PostToolUse already exists — append the matcher block to the array. Don't overwrite existing entries.
Enable on Linux / macOS (bash)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/skills/session-handoff/hooks/commit-detector.sh"
}
]
}
]
}
}
The bash variant needs python3 on PATH (used to parse the PostToolUse JSON stdin).
What gets signalled
On a successful git commit whose subject prefix is not in {meta, docs, style, chore} or fix typo, and whose body exceeds 200 characters or which touches more than 3 files — the hook emits a hookSpecificOutput.additionalContext system reminder of shape:
Substantive commit detected on
<cwd>:<subject>(N files changed, body M chars). Consider invoking session-handoff write-mode to update.tasks/NEXT_SESSION.md.
On any of these → silent skip (exit 0, no JSON):
- malformed PostToolUse stdin
- Bash command isn't
git commit - command is
git commit --amend - commit returned non-zero exit
- cwd isn't a git work-tree
- subject prefix is in trivial set
- body ≤ 200 chars AND files ≤ 3
Smoke test (without enabling the hook)
Pipe a synthetic PostToolUse JSON to the script. On Windows:
$payload = @{
tool_input = @{ command = 'git commit -m "subject"' }
tool_response = @{ exit_code = 0 }
cwd = (Get-Location).Path
} | ConvertTo-Json -Compress
$payload | pwsh -NoProfile -File .\skills\session-handoff\hooks\commit-detector.ps1
If your HEAD is a non-trivial commit (e.g. recent feat: with > 200-char body or > 3 files), output is JSON containing additionalContext. Otherwise — empty stdout (silent skip).
Caveats
- Rebase / cherry-pick noise. Every commit in a rebase or cherry-pick batch will re-fire the hook. Deferred to a follow-up if it actually annoys in practice; the hook is opt-in so the cost is bounded.
- First-non-trivial commit of session. The agent-side heuristic in SKILL.md treats the first non-trivial commit of a session as "always substantive" regardless of thresholds. The hook can't see session boundaries — uses only body/file thresholds. Slight under-detection on small first commits; acceptable trade-off for harness-side determinism.
- No automatic write-mode invocation. Hook only signals. The agent still decides whether to run session-handoff write-mode in response — keeps the user-agency invariant from SKILL.md
What NOT to do.