Build the hook command string written into Claude Code's settings.json. * POSIX embeds a safely quoted absolute custom config path or a portable $HOME * path. Windows explicitly invokes cmd.exe so the hook also works when Claude * falls back from Git Bash to PowerShell. The Windows command defers expansion * of custom paths to cmd.exe and disables delayed expansion, so user path bytes * never
| 2507 | * of custom paths to cmd.exe and disables delayed expansion, so user path bytes |
| 2508 | * never become source text in either outer shell. */ |
| 2509 | static int cbm_build_claude_hook_command(const char *script_name, const char *config_dir, |
| 2510 | bool windows, char *out, size_t out_sz) { |
| 2511 | if (!cbm_hook_script_name_safe(script_name) || !out || out_sz == 0U) { |
| 2512 | return CLI_ERR; |
| 2513 | } |
| 2514 | out[0] = '\0'; |
| 2515 | if (windows) { |
| 2516 | const char *base = |
| 2517 | config_dir && config_dir[0] ? "%CLAUDE_CONFIG_DIR%" : "%USERPROFILE%\\.claude"; |
| 2518 | int written = snprintf(out, out_sz, "cmd.exe /d /v:off /s /c '\"\"%s\\hooks\\%s\"\"'", base, |
| 2519 | script_name); |
| 2520 | return written > 0 && (size_t)written < out_sz ? CLI_OK : CLI_ERR; |
| 2521 | } |
| 2522 | if (config_dir && config_dir[0]) { |
| 2523 | char path[CLI_BUF_1K]; |
| 2524 | int written = snprintf(path, sizeof(path), "%s/hooks/%s", config_dir, script_name); |
| 2525 | return written > 0 && (size_t)written < sizeof(path) |
| 2526 | ? cbm_shell_quote_word(path, out, out_sz) |
| 2527 | : CLI_ERR; |
| 2528 | } |
| 2529 | int written = snprintf(out, out_sz, "\"$HOME/.claude/hooks/%s\"", script_name); |
| 2530 | return written > 0 && (size_t)written < out_sz ? CLI_OK : CLI_ERR; |
| 2531 | } |
| 2532 | |
| 2533 | static int cbm_resolve_hook_command(const char *script_name, char *out, size_t out_sz) { |
| 2534 | char env_buf[CLI_BUF_1K]; |
no test coverage detected