Hooks cross-platform pour Claude Code
Hooks Claude Code cross-platform : supprime les wrappers .cmd, .sh et .ps1 et invoque node directement pour qu'un seul fichier .mjs tourne sur macOS, Linux et Windows dans toute l'équipe.
Arrêtez de configurer. Commencez à construire.
Templates SaaS avec orchestration IA.
Problème : un hook que tu as livré depuis Windows via cmd /c ou PowerShell passe au rouge dès qu'un coéquipier Linux ouvre le repo. La solution de contournement sur laquelle la plupart des gens atterrissent est moche. Trois scripts shim par hook : un .cmd pour Windows, un .sh pour Linux, un .ps1 pour PowerShell. Les trois font exactement la même chose, c'est-à-dire appeler le fichier .mjs qui compte vraiment.
Solution rapide : jette les wrappers. Pointe la config du hook directement sur Node.js :
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/formatter.mjs"
}
]
}
]
}
}Tourne sur Windows, Linux et macOS sans modification. node est toujours dans le $PATH parce que Claude Code liste Node.js comme dépendance obligatoire.
Pourquoi les hooks cassent quand l'OS change
Si personne d'autre ne touche ton setup, rien de tout ça ne te mord. Le problème commence dès que .claude/settings.json est partagé, que le repo devient public, ou que tu alternes entre une tour Windows et un laptop macOS. Dès que bash ou powershell apparaît dans une commande, la moitié de l'équipe ne peut pas l'exécuter.
La plupart des tutoriels sont de toute façon spécifiques à une plateforme :
// Windows-only
"command": "cmd /c \".claude\\hooks\\formatter.cmd\""
// Linux-only
"command": "bash .claude/hooks/formatter.sh"
// PowerShell-only
"command": "powershell -NoProfile -File .claude/hooks/statusline.ps1"Chacun de ces wrappers est deux lignes de boilerplate autour d'un appel node. Trois fichiers, trois plateformes, même boulot sur toutes. Si la seule couche qui connaît l'OS est le shim, tu peux supprimer le shim.
Le pattern universel dans settings.json
Les hooks dans settings.json partagent une seule forme :
{
"statusLine": {
"type": "command",
"command": "node .claude/hooks/statusline-monitor.mjs"
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/skill-activation.mjs"
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/backup.mjs",
"async": true
}
]
}
]
}
}Pas de cmd /c. Pas de bash. Pas de powershell. Juste node. Chaque type de hook prend la même forme, donc PostToolUse, SessionStart/SessionEnd, Stop, et les 12 événements du cycle de vie se câblent de façon identique.
Trois règles pour une logique de hook cross-platform
Dans chaque fichier .mjs, trois habitudes gardent la logique portable quel que soit l'OS qui finit par l'exécuter.
Utilise os.homedir() plutôt que les variables de plateforme
import { homedir } from "os";
import { join } from "path";
// Cross-platform: resolves to C:\Users\you or /home/you
const settingsPath = join(homedir(), ".claude", "settings.json");Garde $HOME, $env:USERPROFILE, et %USERPROFILE% hors du code source. Laisse le helper choisir le bon.
Utilise os.tmpdir() pour les chemins de fichiers temporaires
import { tmpdir } from "os";
import { join } from "path";
// Cross-platform: resolves to C:\Users\you\AppData\Local\Temp or /tmp
const cacheFile = join(tmpdir(), "my-hook-cache.json");Ne va pas chercher /tmp ou $env:TEMP à la main.
Utilise path.join() pour toute construction de chemin de fichier
import { join } from "path";
// Cross-platform path construction
const logFile = join(".claude", "hooks", "logs", "hook.log");Arrête de coller des chemins ensemble avec / ou \\ à la main. Node.js connaît déjà le bon séparateur pour l'OS sur lequel le hook atterrit.
Permissions qui couvrent les deux plateformes
Le bloc permissions dans settings.json doit contenir le nom Windows et le nom Unix côte à côte :
{
"permissions": {
"allow": [
"Bash(where:*)",
"Bash(which:*)",
"Bash(tasklist:*)",
"Bash(ps:*)",
"Bash(taskkill:*)",
"Bash(kill:*)",
"Bash(findstr:*)",
"Bash(node:*)"
]
}
}Quelle que soit la commande qui n'est pas installée sur la machine actuelle, elle reste silencieuse. Lister les deux ne coûte rien, et ton hook choisit celle qui existe vraiment sans déclencher de dialog de permission. Pour une automatisation plus poussée de ce côté, consulte le guide Permission Hook.
Exemple complet : logger de fichiers cross-platform
Un hook complet, portable tel quel. Le boulot est un logger de fichiers. Chaque écriture ou modification que Claude effectue est ajoutée à un log :
#!/usr/bin/env node
import { readFileSync, appendFileSync, mkdirSync } from "fs";
import { join } from "path";
const logDir = join(".claude", "hooks", "logs");
mkdirSync(logDir, { recursive: true });
try {
const input = JSON.parse(readFileSync(0, "utf-8"));
const toolName = input.tool_name;
const filePath = input.tool_input?.file_path || "unknown";
const timestamp = new Date().toISOString();
appendFileSync(
join(logDir, "file-changes.log"),
`${timestamp} | ${toolName} | ${filePath}\n`,
);
} catch {
// Silent fail -- don't block Claude
}
process.exit(0);Enregistre-le dans ton settings.json :
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/file-logger.mjs"
}
]
}
]
}
}Même comportement sur Windows 11, Arch Linux, et macOS Sequoia. Zéro wrapper.
Déboguer quand un OS casse
Tu as un hook qui tourne sur une machine et échoue sur l'autre ? Parcours cette liste de haut en bas :
- Séparateurs de chemin codés en dur. Cherche dans chaque fichier
.mjsles/ou\\qui apparaissent dans un chemin. Passe ces cas àpath.join(). - Références à des variables d'environnement. Cherche
process.env.HOME,process.env.USERPROFILE, ouprocess.env.TEMPet remplace chacun paros.homedir()ouos.tmpdir(). - Commandes spécifiques à un shell dans settings.json. Toute entrée
commandqui mentionnebash,cmd,powershell, oushcasse partout sauf sur son OS natal. Pointe surnode.
Déclenche le hook manuellement pour que l'échec ne puisse pas se cacher :
echo '{"tool_name":"Write","tool_input":{"file_path":"test.js"}}' | node .claude/hooks/your-hook.mjs
echo $? # Should output 0Un 0 sur un OS et autre chose sur l'autre te pointe vers la gestion des chemins dans le fichier .mjs, jamais vers l'entrée du hook dans les settings.
Checklist de mise en production
Parcours cette liste avant un déploiement en équipe ou une mise en production publique :
- Chaque
commanddanssettings.jsonpointe versnode, pascmd,powershell, oubash - Les répertoires home viennent de
os.homedir(), jamais de$HOMEou%USERPROFILE% - Les chemins temporaires viennent de
os.tmpdir(), jamais de/tmpou$env:TEMP - Les chemins sont construits avec
path.join(), pas des séparateurs tapés à la main - Les permissions listent les équivalents Windows et Unix
- La commande
statusLinese résout versnode, paspowershell
Un fichier. Trois plateformes. Zéro maintenance.
Est-ce que les hooks Claude Code fonctionnent sur Windows ?
Oui. Appelle-les via node plutôt qu'un shell qui ne vit que sur un OS, et ils tournent pareil sur Windows, Linux et macOS. Node.js est une dépendance obligatoire de Claude Code sur toutes les plateformes, donc node est toujours dans le $PATH. Mets node .claude/hooks/your-hook.mjs dans settings.json et tu obtiens un comportement identique sur les trois.
Puis-je utiliser Python plutôt que Node.js pour les hooks ?
Python fonctionne aussi, à condition que chaque coéquipier ait déjà un interpréteur sur sa machine. Dans le champ command, utilise python3 plutôt que python, parce que certaines distros Linux ne livrent jamais un simple python. Node.js reste le choix le plus sûr : Claude Code le garantit sur toutes les plateformes, Python non.
Comment gérer les fins de ligne entre plateformes ?
Tu n'as généralement pas à le faire. readFileSync et writeFileSync normalisent déjà les fins de ligne. Chaque hook lit du JSON depuis stdin, et le parser JSON ne se soucie pas de savoir si les retours à la ligne sont CRLF ou LF. L'exception est un hook qui génère son propre script shell. Pour ce cas, écris \n et laisse le reste au paramètre autocrlf de Git.
Arrêtez de configurer. Commencez à construire.
Templates SaaS avec orchestration IA.