Skip to content

fix: add Windows hooks support (PowerShell script + os.tmpdir state file)#2

Open
bertheto wants to merge 2 commits intoofershap:mainfrom
bertheto:fix/windows-hooks-support
Open

fix: add Windows hooks support (PowerShell script + os.tmpdir state file)#2
bertheto wants to merge 2 commits intoofershap:mainfrom
bertheto:fix/windows-hooks-support

Conversation

@bertheto
Copy link

@bertheto bertheto commented Mar 2, 2026

Problem

On Windows, the hooks mode was completely broken for two separate reasons:

1. Bash script not executable on Windows

The hook script hooks/cursor-office-hook.sh requires bash. On Windows there is no bash in PATH by default, so buildHookCommand() produces a command that fails silently. The hooks are registered in hooks.json but do nothing.

2. Hardcoded /tmp path

hooksInstaller.ts hardcodes STATE_FILE = '/tmp/cursor-office-state.json'. On Windows /tmp does not exist, so startHooksWatcher() polls a file that can never be created.

The result: hooks mode appears active (the .sh marker is present in hooks.json) but the office character never reacts to any agent activity.

Solution

hooks/cursor-office-hook.ps1 (new)

PowerShell equivalent of cursor-office-hook.sh. Reads the hook event JSON from $input (stdin), maps tool names to activity strings using a switch, writes JSON state to $env:TEMP\cursor-office-state.json.

Supports the same events: preToolUse, subagentStart, subagentStop, stop, beforeSubmitPrompt.

src/hooksInstaller.ts

  • IS_WINDOWS: os.platform() === 'win32', exported for use in the watcher.
  • STATE_FILE: uses os.tmpdir() on Windows, /tmp on Unix (unchanged).
  • getHookScriptPath(): returns .ps1 on Windows, .sh on Unix.
  • buildHookCommand(): uses powershell.exe -NoProfile -ExecutionPolicy Bypass -File "..." on Windows, bash "..." on Unix.

src/cursorWatcher.ts

On Windows, %TEMP% is extremely busy — fs.watch() on that directory fires hundreds of events per second and would cause CPU spikes. The method startHooksWatcher() already starts a 1-second polling interval, which is sufficient. The fs.watch() call is now skipped on Windows.

Testing

Tested on Windows 10 (22H2) with Cursor 0.47. After:

  1. Copying cursor-office-hook.ps1 into the extension's hooks/ folder
  2. Adding the hook entries to ~/.cursor/hooks.json
  3. Reloading Cursor

The office character reacts correctly in real time: sitting at desk and typing when the agent is thinking, reading icon when using Read/Grep/Glob, running icon for Shell, editing for StrReplace/Write, phone call for Task (subagent delegation), and celebrating on task completion.

…ile)

On Windows, the hooks mode was silently broken for two reasons:
1. The hook script is a bash `.sh` file — not executable on Windows.
2. The state file path was hardcoded to `/tmp/cursor-office-state.json`,
   which does not exist on Windows.

Changes:
- hooks/cursor-office-hook.ps1 (new): PowerShell equivalent of the bash
  hook script. Reads hook event JSON from stdin via $input, maps tool
  names to activity states, writes JSON to %TEMP%\cursor-office-state.json.

- src/hooksInstaller.ts: detect Windows via os.platform() === 'win32'.
  - STATE_FILE now uses os.tmpdir() on Windows, /tmp on Unix.
  - getHookScriptPath() returns .ps1 on Windows, .sh on Unix.
  - buildHookCommand() uses powershell.exe -NoProfile -ExecutionPolicy
    Bypass -File on Windows, bash on Unix.
  - IS_WINDOWS exported for use in cursorWatcher.ts.

- src/cursorWatcher.ts: skip fs.watch() on the state file directory on
  Windows (%TEMP% is too busy and generates excessive noise). The 1s
  polling interval started by startHooksWatcher() is sufficient on Windows.
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses and resolves critical issues that prevented the Cursor Office hooks mode from functioning correctly on Windows. By introducing platform-specific scripts and path handling, and optimizing file system watching for Windows environments, the changes ensure that the office character now accurately reacts to agent activity, providing a consistent user experience across operating systems.

Highlights

  • Windows Hooks Support: Introduced a new PowerShell script (cursor-office-hook.ps1) to enable the Cursor Office hook functionality on Windows, replacing the previously non-functional Bash script.
  • Dynamic State File Path: Modified the state file path (STATE_FILE) in hooksInstaller.ts to dynamically use os.tmpdir() on Windows and retain /tmp on Unix-like systems, resolving issues with hardcoded paths.
  • Platform-Specific Hook Commands: Updated hooksInstaller.ts to generate platform-specific hook commands, using powershell.exe for Windows and bash for Unix, ensuring correct execution.
  • Optimized Windows File Watching: Implemented a conditional check in cursorWatcher.ts to disable fs.watch() on Windows for the state file directory, preventing CPU spikes due to high activity in %TEMP%, and relying solely on the existing 1-second polling interval.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • hooks/cursor-office-hook.ps1
    • Added a new PowerShell script to handle Cursor Office hook events on Windows.
    • Reads JSON input from stdin, processes hook events, and writes activity state to a temporary JSON file.
    • Supports preToolUse, subagentStart, subagentStop, stop, and beforeSubmitPrompt events, mapping them to specific office character activities.
  • src/cursorWatcher.ts
    • Imported IS_WINDOWS constant from hooksInstaller.ts.
    • Modified startHooksWatcher() to conditionally apply fs.watch() only on non-Windows platforms, avoiding performance issues on Windows' %TEMP% directory.
  • src/hooksInstaller.ts
    • Defined and exported IS_WINDOWS constant based on os.platform().
    • Updated STATE_FILE to use os.tmpdir() on Windows and /tmp on other platforms.
    • Modified getHookScriptPath() to return the appropriate script (.ps1 for Windows, .sh for Unix).
    • Adjusted buildHookCommand() to generate PowerShell commands for Windows and Bash commands for Unix.
Activity
  • The pull request was created by bertheto to fix Windows hooks support.
  • The author provided a detailed problem description, solution overview, and testing steps.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for Windows hooks by introducing a PowerShell script and making the necessary adjustments in the TypeScript code to handle platform differences. The changes are well-structured and address the issues described. I have one suggestion for the new PowerShell script to improve its robustness by using built-in JSON handling cmdlets instead of manual parsing and string construction.

Comment on lines +4 to +45
$input_data = $input | Out-String

function Get-JsonValue {
param([string]$json, [string]$key)
if ($json -match """$key""\s*:\s*""([^""]+)""") { return $Matches[1] }
return ""
}

$event = Get-JsonValue $input_data "hook_event_name"
$ts = [int][double]::Parse((Get-Date -UFormat %s))

switch ($event) {
"preToolUse" {
$tool = Get-JsonValue $input_data "tool_name"
$activity = switch ($tool) {
{ $_ -in "Read","Glob","SemanticSearch","Grep" } { "reading" }
{ $_ -in "Write","StrReplace","EditNotebook","Delete" } { "editing" }
"Shell" { "running" }
"Task" { "phoning" }
default { "typing" }
}
"{""activity"":""$activity"",""tool"":""$tool"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8
}
"subagentStart" {
"{""activity"":""phoning"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8
}
"subagentStop" {
"{""activity"":""typing"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8
}
"stop" {
$status = Get-JsonValue $input_data "status"
$activity = switch ($status) {
"completed" { "celebrating" }
"error" { "error" }
default { "idle" }
}
"{""activity"":""$activity"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8
}
"beforeSubmitPrompt" {
"{""activity"":""idle"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a custom regex-based function to parse JSON and manual string building for output is fragile. It's better to use PowerShell's built-in ConvertFrom-Json and ConvertTo-Json cmdlets for robust JSON handling. This refactoring also centralizes the file-writing logic, making the script cleaner and easier to maintain.

$input_data = $input | Out-String
$data = $input_data | ConvertFrom-Json

$event = $data.hook_event_name
$ts    = [int][double]::Parse((Get-Date -UFormat %s))

$state = $null

switch ($event) {
  "preToolUse" {
    $tool = $data.tool_name
    $activity = switch ($tool) {
      { $_ -in "Read","Glob","SemanticSearch","Grep" } { "reading" }
      { $_ -in "Write","StrReplace","EditNotebook","Delete" } { "editing" }
      "Shell" { "running" }
      "Task"  { "phoning" }
      default { "typing" }
    }
    $state = @{ activity = $activity; tool = $tool; ts = $ts }
  }
  "subagentStart" {
    $state = @{ activity = "phoning"; ts = $ts }
  }
  "subagentStop" {
    $state = @{ activity = "typing"; ts = $ts }
  }
  "stop" {
    $status = $data.status
    $activity = switch ($status) {
      "completed" { "celebrating" }
      "error"     { "error" }
      default     { "idle" }
    }
    $state = @{ activity = $activity; ts = $ts }
  }
  "beforeSubmitPrompt" {
    $state = @{ activity = "idle"; ts = $ts }
  }
}

if ($state) {
    $state | ConvertTo-Json -Compress | Set-Content $stateFile -Encoding UTF8
}

Replace the custom regex-based JSON parsing and manual string building
with PowerShell's built-in ConvertFrom-Json and ConvertTo-Json cmdlets.
Centralizes file-writing via a $state hashtable, making the script more
robust and easier to maintain.

Suggested by Gemini Code Assist review on PR ofershap#2.
@bertheto
Copy link
Author

bertheto commented Mar 2, 2026

Fixed in 7103e52: replaced the regex-based \Get-JsonValue\ function and manual string building with \ConvertFrom-Json\ / \ConvertTo-Json -Compress. All switch branches now populate a \\ hashtable, and a single \ConvertTo-Json | Set-Content\ at the end writes the file. Much cleaner.

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