Skip to content

buildHookCommand writes an unquoted command, breaking on Windows under bash -c #1

Open

Description

@codeforheber

buildHookCommand writes the hook command unquoted, which breaks on Windows when Claude Code invokes it via bash -c

Summary

commitly init on Windows writes a hooks.* entry into ~/.claude/settings.json whose command string is the resolved executable path joined with the rest of the argv via a plain .join(" "), with no quoting around any element. On Windows, Claude Code runs hook commands through bash -c "<command>". Bash interprets an unquoted backslash as an escape character, so a Windows path like C:\Users\<user>\.bun\bin\commitly.exe gets mangled into C:UsersDELL.bunbincommitly.exe (each \ + letter pair is silently collapsed whenever the letter isn't a recognized bash escape sequence).

Root cause

src/hooks/install.ts:

export async function resolveCommitlyArgv(): Promise<string[]> {
  const which = await Bun.$`which commitly`.quiet().nothrow();
  const resolved = which.exitCode === 0 ? which.stdout.toString().trim() : "";
  if (resolved) return [resolved];

  const cliPath = new URL("../cli.ts", import.meta.url).pathname;
  return ["bun", cliPath];
}

export async function buildHookCommand(tool: string, event: string): Promise<string> {
  const argv = await resolveCommitlyArgv();
  return [...argv, "hook", tool, event].join(" ");
}

Neither the resolved executable path nor any other argv element is quoted before being joined into the final shell command string.

Observed failure (Windows, real install)

After commitly init --scope user --tools claude-code, the written SessionStart/Stop/SessionEnd hook commands looked like:

C:\Users\<user>\.bun\bin\commitly.exe hook claude-code SessionStart

Claude Code on Windows runs this via bash, and every subsequent session logged a non-blocking hook failure:

Failed with non-blocking status code: /usr/bin/bash: line 1:
C:UsersDELL.bunbincommitly.exe: command not found

(username redacted; this is a real, reproducible install, not a hypothetical)

As a result, commitly doctor reported 0 event(s) across 0 session(s) — the hook was silently failing on every session, and no events were ever captured.

Fix applied locally (workaround, not a PR)

Manually quoting the executable path in the three written command strings ("C:\Users\<user>\.bun\bin\commitly.exe" hook claude-code SessionStart) resolved it immediately — commitly doctor went from 0 event(s) to real captured events after the fix, and a fresh Claude Code session with the corrected hook produced no error.

Suggested fix

Quote each argv element in buildHookCommand (and anywhere else a resolved path is joined into a shell command string) before joining, e.g. wrapping any element containing a backslash, space, or other shell-significant character in double quotes. This would need to hold for both the which commitly resolution path and the ["bun", cliPath] fallback path, since both can contain Windows-style paths.

Environment

  • OS: Windows (Claude Code hooks run through bash on this platform)
  • commitly installed via git clone + bun install + bun link (per this repo's own README, since commitly on npm is an unrelated package)
  • Bun 1.4.2

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions