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
| 2550 | * of custom paths to cmd.exe and disables delayed expansion, so user path bytes |
| 2551 | * never become source text in either outer shell. */ |
| 2552 | static int cbm_build_claude_hook_command(const char *script_name, const char *config_dir, |
| 2553 | bool windows, char *out, size_t out_sz) { |
| 2554 | if (!cbm_hook_script_name_safe(script_name) || !out || out_sz == 0U) { |
| 2555 | return CLI_ERR; |
| 2556 | } |
| 2557 | out[0] = '\0'; |
| 2558 | if (windows) { |
| 2559 | const char *base = |
| 2560 | config_dir && config_dir[0] ? "%CLAUDE_CONFIG_DIR%" : "%USERPROFILE%\\.claude"; |
| 2561 | int written = snprintf(out, out_sz, "cmd.exe /d /v:off /s /c '\"\"%s\\hooks\\%s\"\"'", base, |
| 2562 | script_name); |
| 2563 | return written > 0 && (size_t)written < out_sz ? CLI_OK : CLI_ERR; |
| 2564 | } |
| 2565 | if (config_dir && config_dir[0]) { |
| 2566 | char path[CLI_BUF_1K]; |
| 2567 | int written = snprintf(path, sizeof(path), "%s/hooks/%s", config_dir, script_name); |
| 2568 | return written > 0 && (size_t)written < sizeof(path) |
| 2569 | ? cbm_shell_quote_word(path, out, out_sz) |
| 2570 | : CLI_ERR; |
| 2571 | } |
| 2572 | int written = snprintf(out, out_sz, "\"$HOME/.claude/hooks/%s\"", script_name); |
| 2573 | return written > 0 && (size_t)written < out_sz ? CLI_OK : CLI_ERR; |
| 2574 | } |
| 2575 | |
| 2576 | static int cbm_resolve_hook_command(const char *script_name, char *out, size_t out_sz) { |
| 2577 | char env_buf[CLI_BUF_1K]; |
no test coverage detected