Repository navigation
Description
Problem
Every tool call in a local Claude session fails, including built-in tools and relay's MCP tools:
PreToolUse:mcp__relay__fs_list hook error: [<bundle>/Contents/Helpers/relay-sessions]: usage: relay-sessions <service|exec|hook> [flags]
The session starts and lists its tools, but it can't call any of them.
Root cause
cmd/relaysessions/main.go sets ClaudeConfig.HookCommandPath to the bare relay-sessions binary path. ensureHookConfig (internal/sessions/provider/claude_hook.go) writes HookCommandPath into the project's .claude/settings.local.json unchanged. Only the fallback resolveHookCommand() appends hook. So Claude Code runs relay-sessions with no mode argument, and it exits with its usage error.
Fix
Make the configured path produce <abs>/relay-sessions hook: either append the subcommand in ensureHookConfig for a configured path the same way the fallback does, or make the field hold the full command and set it that way in main.go. One of the two, with a single source of truth. Add a failing test first: a Claude session built from the real relay-sessions config writes a hook command ending in hook.
Contract
ClaudeConfig.HookCommandPathkeeps its documented meaning: the absolute path to the relay-sessions binary.cmd/relaysessions/main.gois unchanged.internal/sessions/provider/claude_hook.go: one helper builds the command for both paths.// hookCommand is C6's hook command for a relay-sessions binary: "<binary> hook". func hookCommand(binary string) string
ensureHookConfigwriteshookCommand(p.cfg.HookCommandPath)when the path is set. Otherwise it writeshookCommand(<EvalSymlinks'd os.Executable()>).resolveHookCommandkeeps its signature and result, and uses the helper.- Wire shape unchanged:
.claude/settings.local.json→hooks.PreToolUse[0].hooks[0].command == "<abs binary> hook",timeout: 120,matcher: "". - Tests:
- New, red first:
TestEnsureHookConfig_ConfiguredBinaryPathGetsHookSubcommand.HookCommandPath: "/abs/path/relay-sessions"→ command"/abs/path/relay-sessions hook". - Existing fixtures that set
HookCommandPathto a string already ending inhook(claude_hook_test.go,claude_realspawn_test.go) encode the wrong meaning. They change to the bare binary path, and their assertions on the written command stay the same.
- New, red first:
- Files:
claude_hook.go(dev), the tests above (test writer).
Tasks
- T1 · Failing repro test and fixture correction [Completed - Verified]
- T2 · Fix
ensureHookConfigviahookCommand[Completed - Verified]
Activity