Skip to content

feat(hook): record Claude Code's PowerShell tool invocations - #3911

Open
hallut666 wants to merge 1 commit into
atuinsh:mainfrom
hallut666:feat/claude-code-powershell-tool
Open

feat(hook): record Claude Code's PowerShell tool invocations#3911
hallut666 wants to merge 1 commit into
atuinsh:mainfrom
hallut666:feat/claude-code-powershell-tool

Conversation

@hallut666

Copy link
Copy Markdown

Checks

  • I am happy for maintainers to push small adjustments to this PR, to speed up the review cycle
  • I have checked that there are no existing pull requests for the same thing

The problem

On Windows, Claude Code exposes a separate PowerShell tool alongside Bash and routes most shell calls through it. Atuin records none of them, and never says so:

  • 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 invoked the hook for those tools in the first place.

atuin hook install claude-code reports success, the hook entries look right in settings.json, and --author claude-code stays empty for every command that didn't go through the Bash tool.

Changes

Accept the tool. A PowerShell variant on WireToolName, and the claude-code install matcher widened to Bash|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_entries decided "already installed" from the hook command alone. 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 people who need it. Atuin still claims an entry by its command, but now rewrites that entry's matcher when it differs. Entries the user configured themselves are left alone.

Docs. agent-hooks.md stated that only Bash invocations are captured.

One thing worth your call

The matcher refresh is a behaviour change: atuin hook install used 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_skipped filtered only the literal "Bash" out of an arbitrary-string generator. Accepting PowerShell would have turned it into a rare flake rather than a stable failure, so it now excludes both shell tools and is renamed non_shell_tool_is_always_skipped. The three shell property tests sample over ["Bash", "PowerShell"] instead of hardcoding Bash, 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-targets and vale on 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 PowerShell tool's PreToolUse/PostToolUse JSON was captured from a live Claude Code session, and a paired-marker test — one marker through the Bash tool, one through PowerShell, same session, same moment — confirmed the Bash-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 PowerShell tool_response carries no exitCode, so successes take the default-zero path and failures arrive as PostToolUseFailure, which already forces exit 1. The existing mapping handles both; no change was needed there.

Note

#3906 touches hook.rs as well (agent config-dir env vars). Different region, but flagging it in case you want to sequence them.

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.
@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds Claude Code PowerShell command recording on Windows and refreshes stale installed matchers.

  • Recognizes PowerShell hook events with the existing shell lifecycle.
  • Installs Bash|PowerShell matchers while preserving unrelated hook entries.
  • Updates tests and agent-hook documentation.

Confidence Score: 5/5

The 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

Filename Overview
crates/atuin/src/command/client/hook.rs Expands Claude’s matcher and updates previously installed Atuin hook entries without duplicating them.
crates/atuin/src/command/client/hook/event.rs Extends event tests to verify equivalent Bash and PowerShell lifecycle behavior.
crates/atuin/src/command/client/hook/wire.rs Adds exact deserialization support for Claude Code’s PowerShell tool name.
docs/docs/guide/agent-hooks.md Documents PowerShell capture and the matcher-refresh reinstall behavior.

Reviews (1): Last reviewed commit: "feat(hook): record Claude Code's PowerSh..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant