feat(trace): skip Claude Code and Cursor hooks when the Chainloop CLI is missing
Closes #3687
The Claude Code and Cursor hooks run chainloop trace hook … directly, so in a repository with the hooks committed but without the CLI on PATH, every hook fails with exit 127 before Chainloop's best-effort handling can run. This wraps them in the guard from the issue, the same one #3684 added for Codex:
sh -c 'if ! command -v chainloop >/dev/null 2>&1; then exit 0; fi; exec chainloop trace hook claude post-tool-use'
- A new
trace.GuardHookCommand(command)builds that string. Both providers use it, so the guard is defined in one place. - Claude Code (
.claude/settings.json):buildHookEntrywrites the guarded command.addHookEntrynow treats an entry as up to date only if both its matcher and its Chainloop command match. Previously it compared only the matcher, so an unguarded entry from an earliertrace initwould never have been upgraded. A smallchainloopHookCommandhelper finds the Chainloop handler in a group. - Cursor (
.cursor/hooks.json):buildHookEntrywrites the guarded command, andaddHookEntrycompares against it. Cursor's flat entry format is unchanged. - Unchanged as the issue asks: user hooks are untouched,
trace initstays idempotent, and the Git hooks and the pre-pushrequireTraceenforcement are untouched.execpasses stdin and stdout through and keeps the CLI's exit status when it's present.
Tests, in both claude/hooks_test.go and cursor/hooks_test.go:
TestInstalledHooksWithoutCLIruns every hook command withPATHholding onlysh. Each one exits 0 with no output, and every installed command contains the guard.TestInstalledHookPreservesIOAndExitStatusruns against a stubchainloopthat echoes its arguments and stdin and exits with 0 or 37. The output and exit code come through unchanged.TestInstallUpgradesUnguardedHooksstarts from a settings file with a barechainloop trace hook … post-tool-useentry next to a user hook.InstallHooksreplaces it with exactly one guarded entry, keeps the user hook, and a second run changes nothing.- The existing command assertions now expect the guarded form.
go test ./app/cli/internal/trace/..., go vet and gofmt -l are clean (Go 1.27.3, offline). Against main's hooks.go the new install and upgrade tests fail. The runtime tests skip on Windows, as Codex's do.
One question: Codex also sets a commandWindows with a cmd.exe/where guard. As far as I know Claude Code needs Git Bash on Windows, so sh should be available there, but I couldn't check it on Windows. I don't know of an equivalent override field for Cursor, so Cursor on Windows needs sh on PATH. If that's a concern, I can make the Cursor guard platform-specific at install time.