feat(hook): record Claude Code's PowerShell tool invocations - #3911
Open
hallut666 wants to merge 1 commit into
Open
feat(hook): record Claude Code's PowerShell tool invocations#3911hallut666 wants to merge 1 commit into
hallut666 wants to merge 1 commit into
Conversation
Claude Code on Windows exposes a separate `PowerShell` tool alongside
`Bash` and routes most shell calls through it. Two things dropped those
events on the floor:
- `WireToolName` only modelled `Bash`, so a `PowerShell` event decoded
to `Other` and `From<WireHookEvent>` returned `None`.
- `atuin hook install claude-code` wrote a `"Bash"` matcher, so the
agent never even invoked the hook for those tools.
The result is that on Windows the agent's shell history is silently
never recorded: `atuin hook install claude-code` reports success and
`--author claude-code` stays empty for every command not run through the
Bash tool.
Add a `PowerShell` variant to `WireToolName` and widen the claude-code
install matcher to `Bash|PowerShell`. The serde variant name matches the
wire value exactly, so no rename is needed. Codex keeps `^Bash$` and the
extension-based agents are untouched.
Refresh stale matchers on re-install. `add_hook_entries` decided
"already installed" from the hook `command` alone, so anyone who
installed before this change would re-run install, be told the hooks
were already there, and keep the old `Bash`-only matcher — the fix would
never reach the users who need it. Atuin still claims an entry by its
command, but now rewrites that entry's matcher when it differs, leaving
hooks the user configured themselves untouched.
Update the agent-hooks guide, which stated that only `Bash` invocations
are captured.
Note on the tests: `non_bash_tool_is_always_skipped` filtered only the
literal `"Bash"` out of an arbitrary-string generator, so accepting
`PowerShell` would have turned it into a rare flake rather than a stable
failure. It now excludes both shell tools and is renamed to
`non_shell_tool_is_always_skipped`. The three shell property tests
sample over `["Bash", "PowerShell"]` instead of hardcoding `Bash`, which
asserts the two tools share semantics rather than only spot-checking the
new one.
Contributor
Greptile SummaryAdds Claude Code PowerShell command recording on Windows and refreshes stale installed matchers.
Confidence Score: 5/5The PR appears safe to merge. Exact wire-name filtering preserves existing non-shell behavior, PowerShell events follow the established history lifecycle, and matcher refresh remains scoped to entries invoking Atuin’s own hook command. Important Files Changed
Reviews (1): Last reviewed commit: "feat(hook): record Claude Code's PowerSh..." | Re-trigger Greptile |
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.
Checks
The problem
On Windows, Claude Code exposes a separate
PowerShelltool alongsideBashand routes most shell calls through it. Atuin records none of them, and never says so:WireToolNameonly modelledBash, so aPowerShellevent decoded toOtherandFrom<WireHookEvent>returnedNone.atuin hook install claude-codewrote a"Bash"matcher, so the agent never invoked the hook for those tools in the first place.atuin hook install claude-codereports success, the hook entries look right insettings.json, and--author claude-codestays empty for every command that didn't go through the Bash tool.Changes
Accept the tool. A
PowerShellvariant onWireToolName, and the claude-code install matcher widened toBash|PowerShell. The serde variant name matches the wire value exactly, so no rename is needed. Codex keeps^Bash$; the extension-based agents are untouched.Refresh stale matchers on re-install.
add_hook_entriesdecided "already installed" from the hookcommandalone. Anyone who installed before this change would re-run install, be told the hooks were already there, and keep the oldBash-only matcher — the fix would never reach the people who need it. Atuin still claims an entry by its command, but now rewrites that entry'smatcherwhen it differs. Entries the user configured themselves are left alone.Docs.
agent-hooks.mdstated that onlyBashinvocations are captured.One thing worth your call
The matcher refresh is a behaviour change:
atuin hook installused to only ever append, and now edits an existing entry in place. I think it belongs here, because without it this fix silently does nothing for existing users — the same failure mode the PR is about — and there are tests covering that foreign entries stay untouched. But if you'd rather keep install append-only, or take that part as its own PR, say the word and I'll split it.On the tests
non_bash_tool_is_always_skippedfiltered only the literal"Bash"out of an arbitrary-string generator. AcceptingPowerShellwould have turned it into a rare flake rather than a stable failure, so it now excludes both shell tools and is renamednon_shell_tool_is_always_skipped. The three shell property tests sample over["Bash", "PowerShell"]instead of hardcodingBash, asserting the two tools share semantics rather than spot-checking the new one.Verification
On a Linux codespace:
cargo fmt --check,cargo test -p atuin hook(44 passed),cargo clippy -p atuin --all-targetsandvaleon the changed doc — all clean.I could not verify a built binary on real Windows, as I don't have a Windows Rust toolchain. What was verified on Windows is the payload shape and the failure itself: the
PowerShelltool'sPreToolUse/PostToolUseJSON was captured from a live Claude Code session, and a paired-marker test — one marker through theBashtool, one throughPowerShell, same session, same moment — confirmed theBash-only matcher records the former and silently drops the latter. The parsing side is covered by the tests above.One detail from that capture, in case it's useful: Claude Code's
PowerShelltool_responsecarries noexitCode, so successes take the default-zero path and failures arrive asPostToolUseFailure, which already forces exit 1. The existing mapping handles both; no change was needed there.Note
#3906 touches
hook.rsas well (agent config-dir env vars). Different region, but flagging it in case you want to sequence them.