Cross-Platform Claude Code Hooks: Write Once, Run on Windows, Linux, and macOS
Claude Code hooks that work on Windows, Linux, and macOS. One Node.js file, zero platform wrappers. Cross-platform patterns, permissions, and full working examples.
Agentic Orchestration Kit for Claude Code.
Problem: You built a Claude Code hook on Windows using cmd /c or PowerShell. A teammate on Linux opens the project and every hook throws errors. Now you're maintaining three wrapper scripts per hook -- .cmd for Windows, .sh for Linux, .ps1 for PowerShell -- and they all do the same thing: call the actual .mjs file.
Quick Win: Delete every wrapper. Invoke Node.js directly in your Claude Code hooks config:
This works on Windows, Linux, and macOS. Claude Code requires Node.js, so node is always available.
Why Claude Code Hooks Break on Different Operating Systems
If you're the only person using your Claude Code setup, platform compatibility isn't a concern. But the moment you share .claude/settings.json with a teammate, open-source a project, or switch between a Windows workstation and a macOS laptop, platform-specific hooks become a maintenance burden. Every hook that uses bash or powershell in the command field is a hook that breaks on half your team's machines.
Most tutorials show platform-specific invocations:
Each wrapper is a 2-line file that just calls node. Three files maintained across three platforms, all doing the same thing. When the wrapper is the only platform-dependent layer, eliminate it.
How to Write Claude Code Hooks for Windows, Linux, and macOS
Every hook in settings.json follows this universal pattern:
No cmd /c. No bash. No powershell. Just node. This pattern works for every Claude Code hook type -- PostToolUse, SessionStart/SessionEnd, Stop, and all 12 lifecycle events.
Three Rules for Cross-Platform Hook Logic
Inside your .mjs files, three rules keep Claude Code hooks universal across Windows, Linux, and macOS:
Use os.homedir() Instead of Platform Variables
Never hardcode $HOME, $env:USERPROFILE, or %USERPROFILE%.
Use os.tmpdir() for Temporary File Paths
Never hardcode /tmp or $env:TEMP.
Use path.join() for All File Path Construction
Never concatenate paths with / or \\. Node.js handles the separator for each OS automatically.
Setting Up Cross-Platform Permissions in Claude Code
Your settings.json permissions should include equivalents for both platforms:
Commands that don't exist on a platform simply won't be used. Including both costs nothing, and your Claude Code hooks can reference whichever command is available without hitting permission prompts. For more advanced permission automation, see the Permission Hook guide.
Complete Example: Cross-Platform File Logger Hook
Here's a complete Claude Code hook that works everywhere -- a file logger that records every Write/Edit operation:
Register it in your settings.json:
Works identically on Windows 11, Arch Linux, and macOS Sequoia. No wrappers needed.
Debugging Cross-Platform Hook Issues
When a Claude Code hook works on one OS but fails on another, check these three things in order:
- Hardcoded path separators. Search your
.mjsfiles for/or\\used in file paths. Replace withpath.join(). - Environment variable references. Look for
process.env.HOME,process.env.USERPROFILE, orprocess.env.TEMP. Replace withos.homedir()andos.tmpdir(). - Shell-specific commands in settings.json. Any
commandfield containingbash,cmd,powershell, orshbreaks on other platforms. Replace withnode.
Run hooks manually to isolate the failure:
If it exits 0 on one OS and fails on another, the issue is in the .mjs file's path handling, not the hook configuration.
Cross-Platform Release Checklist
Before shipping Claude Code hooks to a team or open-source project, verify:
- All
settings.jsoncommands usenode(notcmd,powershell,bash) - All
.mjsfiles useos.homedir()(not$HOMEor%USERPROFILE%) - All
.mjsfiles useos.tmpdir()(not/tmpor$env:TEMP) - All
.mjsfiles usepath.join()(not hardcoded separators) - Permissions include both Windows and Unix equivalents
- StatusLine command uses
node(notpowershell)
One implementation. Three platforms. Zero maintenance overhead. Every hook in the Code Kit follows this exact checklist -- all 5 hooks are .mjs files invoked with node, using os.homedir(), os.tmpdir(), and path.join() for paths, so they work on Windows, Linux, and macOS without modification.
Frequently Asked Questions
Do Claude Code hooks work on Windows?
Yes. Claude Code hooks work on Windows, Linux, and macOS when you invoke them with node instead of platform-specific shells. Since Claude Code requires Node.js on every platform, the node command is always available. Use node .claude/hooks/your-hook.mjs in settings.json and your hooks run identically on all three operating systems.
Can I use Python instead of Node.js for hooks?
Python works for cross-platform hooks if your team has Python installed everywhere. Use python3 (not python, which may not exist on some Linux distributions) in the command field. However, Node.js is the safer default since Claude Code guarantees its availability on every platform.
How do I handle line endings across platforms?
Node.js handles line endings automatically when using readFileSync and writeFileSync. If you're reading stdin (which all hooks do), the JSON parsing is line-ending agnostic. The only place line endings matter is if you're generating shell scripts from a hook -- in that case, use \n and let Git's autocrlf setting handle conversion.
Next Steps
- Read the complete hooks guide for all 12 lifecycle events and exit code patterns
- Set up Context Recovery with cross-platform backup triggers
- Configure Skill Activation for automatic skill loading
- Explore Setup Hooks for cross-platform onboarding
- Master permission rules alongside cross-platform hook permissions
Last updated on