Skip to content

fix(claude): emit PowerShell-compatible hooks on Windows - #4591

Open
Razikale365 wants to merge 2 commits into
getpaseo:mainfrom
Razikale365:fix/claude-windows-hooks
Open

fix(claude): emit PowerShell-compatible hooks on Windows#4591
Razikale365 wants to merge 2 commits into
getpaseo:mainfrom
Razikale365:fix/claude-windows-hooks

Conversation

@Razikale365

Copy link
Copy Markdown

Linked issue

Fixes #4554

Type of change

  • Bug fix
  • New feature
  • Enhancement
  • Refactor
  • Docs

Reasoning

Claude stores one shell-form command per hook. Paseo wrote the POSIX command on every platform, so native Windows Claude installations that select PowerShell fail while parsing [ -n ... ] before the terminal guard can run.

Windows now gets powershell.exe -NoProfile -NonInteractive -EncodedCommand <base64> with a UTF-16LE payload. Encoding avoids nested quoting and shell-tokenization problems. The wrapper preserves the terminal guard, CLI override, PATH fallback, child exit status, and inherited stdin. macOS and Linux keep the existing POSIX command.

The encoded command is opaque, so install detection and uninstall compare it with the exact command Paseo generates while retaining marker matching for legacy POSIX hooks.

Goals

  • Make all generated Claude hooks executable through Windows PowerShell.
  • Preserve PASEO_TERMINAL_ID, PASEO_HOOK_CLI, PATH fallback, exit-code, and stdin behavior.
  • Keep install and uninstall idempotent without removing unrelated user hooks.
  • Keep existing macOS/Linux behavior unchanged.

Non-goals

#3877 remains open and changes the analogous Codex Windows helper. This PR leaves Codex untouched and remains independently reviewable. If #3877 lands first, this branch can be rebased to reuse its encoded PowerShell helper.

QA

Windows 11 with Windows PowerShell 5.1:

  • Reproduced the old command under PowerShell: exit 1 with ParserError.
  • Executed the command read back from an isolated generated Claude settings file.
  • Verified all five no-terminal guards exit 0, and the guarded CLI is not invoked.
  • Verified a space-containing .cmd path, paseo.cmd PATH fallback, .cmd exit 7, native executable exit 2, unresolved-command exit 1, exact arguments, and byte-preserving Notification stdin forwarding.
  • Verified repeat install, legacy-hook cleanup, exact encoded-hook detection, safe uninstall, and unrelated-hook preservation.
cd packages/server
npx vitest run src/terminal/agent-hooks/claude/claude.test.ts src/terminal/agent-hooks/codex/codex.test.ts src/terminal/agent-hooks/terminal-agent-hook-setting.test.ts --bail=1
# 3 files passed; 31 tests passed; 5 POSIX-only tests skipped on Windows

npm run typecheck --workspace=@getpaseo/server
# passed

npm run build:server
npm run typecheck
# passed

npm run lint -- docs/terminal-activity.md packages/server/src/terminal/agent-hooks/agent-hook-installer.ts packages/server/src/terminal/agent-hooks/claude/claude-settings.ts packages/server/src/terminal/agent-hooks/claude/claude.test.ts
# 0 warnings, 0 errors

npm run format:check:files -- docs/terminal-activity.md packages/server/src/terminal/agent-hooks/agent-hook-installer.ts packages/server/src/terminal/agent-hooks/claude/claude-settings.ts packages/server/src/terminal/agent-hooks/claude/claude.test.ts
# passed

Claude Code CLI was not installed in this environment, so an actual Claude session was not run. macOS/Linux runtime QA was not run locally; the POSIX generator is unchanged and its exact command remains covered.

Checklist

  • Plugin changes follow the SDK import boundaries (not applicable; no plugin changes)
  • One focused change
  • npm run typecheck passes
  • npm run lint passes for the changed source files
  • Changed files pass the repository formatter and formatting check
  • QA evidence
  • Tests added or updated where it made sense

@greptile-apps

greptile-apps Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes Claude hooks executable on native Windows installations by generating UTF-16LE base64-encoded PowerShell commands while retaining the existing POSIX commands elsewhere.

  • Adds a PowerShell hook-command generator preserving the terminal guard, CLI override, stdin, and child exit status.
  • Centralizes Claude’s platform-aware command selection across installation, uninstallation, and detection.
  • Adds platform-specific command and Windows runtime coverage.
  • Documents the platform-specific Claude hook behavior and lifecycle matching.

Confidence Score: 5/5

The current changes appear safe to merge, although the existing non-blocking concern about version-stable identity for encoded Windows hooks remains unresolved.

No new actionable failure was introduced since the previous review. The platform discriminator is now centralized, and the tests no longer branch conditionally inside test bodies. The earlier encoded-hook identity finding remains outstanding because lifecycle detection and cleanup still depend on exact equality with the current generated payload, so a future payload change can orphan older hooks. The two rule-linked threads were manually resolved without explanatory replies.

Important Files Changed

Filename Overview
packages/server/src/terminal/agent-hooks/agent-hook-installer.ts Adds the encoded PowerShell command generator with guarded CLI execution and exit-code propagation.
packages/server/src/terminal/agent-hooks/claude/claude-settings.ts Centralizes platform-aware Claude command selection and reuses it throughout hook lifecycle operations; the previously reported lack of stable identity for encoded hooks remains outstanding.
packages/server/src/terminal/agent-hooks/claude/claude.test.ts Splits platform expectations into gated tests and covers Windows execution, stdin forwarding, command resolution, and exit statuses.
docs/terminal-activity.md Documents Claude’s POSIX and encoded PowerShell hook behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  I[Install Claude hooks] --> P{Host platform}
  P -->|Windows| W[Generate encoded PowerShell command]
  P -->|macOS or Linux| S[Generate POSIX shell command]
  W --> C[Write Claude command hook]
  S --> C
  C --> G{PASEO_TERMINAL_ID set?}
  G -->|No| X[Exit successfully]
  G -->|Yes| R[Resolve PASEO_HOOK_CLI or paseo]
  R --> H[Run paseo hooks claude event]
  H --> E[Propagate child exit status]
Loading

Reviews (2): Last reviewed commit: "refactor(claude): centralize hook comman..." | Re-trigger Greptile

Comment thread packages/server/src/terminal/agent-hooks/claude/claude-settings.ts Outdated
Comment thread packages/server/src/terminal/agent-hooks/claude/claude.test.ts Outdated
Comment thread packages/server/src/terminal/agent-hooks/claude/claude-settings.ts
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.

Windows: Paseo-generated Claude Code hooks always fail with a PowerShell ParserError (settings.json gets POSIX-only commands, no Windows variant)

1 participant