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
| 2660 | * of custom paths to cmd.exe and disables delayed expansion, so user path bytes |
| 2661 | * never become source text in either outer shell. */ |
| 2662 | static int cbm_build_claude_hook_command(const char *script_name, const char *config_dir, |
| 2663 | bool windows, char *out, size_t out_sz) { |
| 2664 | if (!cbm_hook_script_name_safe(script_name) || !out || out_sz == 0U) { |
| 2665 | return CLI_ERR; |
| 2666 | } |
| 2667 | out[0] = '\0'; |
| 2668 | if (windows) { |
| 2669 | const char *base = |
| 2670 | config_dir && config_dir[0] ? "%CLAUDE_CONFIG_DIR%" : "%USERPROFILE%\\.claude"; |
| 2671 | int written = snprintf(out, out_sz, "cmd.exe /d /v:off /s /c '\"\"%s\\hooks\\%s\"\"'", base, |
| 2672 | script_name); |
| 2673 | return written > 0 && (size_t)written < out_sz ? CLI_OK : CLI_ERR; |
| 2674 | } |
| 2675 | if (config_dir && config_dir[0]) { |
| 2676 | char path[CLI_BUF_1K]; |
| 2677 | int written = snprintf(path, sizeof(path), "%s/hooks/%s", config_dir, script_name); |
| 2678 | return written > 0 && (size_t)written < sizeof(path) |
| 2679 | ? cbm_shell_quote_word(path, out, out_sz) |
| 2680 | : CLI_ERR; |
| 2681 | } |
| 2682 | int written = snprintf(out, out_sz, "\"$HOME/.claude/hooks/%s\"", script_name); |
| 2683 | return written > 0 && (size_t)written < out_sz ? CLI_OK : CLI_ERR; |
| 2684 | } |
| 2685 | |
| 2686 | static int cbm_resolve_hook_command(const char *script_name, char *out, size_t out_sz) { |
| 2687 | char env_buf[CLI_BUF_1K]; |
no test coverage detected