chenhg5/cc-connect · error
hook exec: %w (stderr: %s)
Error message
hook exec: %w (stderr: %s)
What it means
runHookCommand executes the configured permission hook via `sh -c command` with a 60s timeout and reports failure as `hook exec: %w (stderr: %s)`, wrapping the exec error plus up to 200 bytes of the hook's stderr. This error means the hook command itself failed to run or exited non-zero — the wrapper is surfacing the hook's own diagnostics.
Source
Thrown at agent/claudecode/cc_hooks.go:265
stdinJSON, err := json.Marshal(stdinData)
if err != nil {
return ccHookDecision{}, fmt.Errorf("marshal stdin: %w", err)
}
timeoutCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
cmd := exec.CommandContext(timeoutCtx, "sh", "-c", command)
cmd.Stdin = bytes.NewReader(stdinJSON)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// Strip the skip flag so the hook does real work when cc-connect
// calls it (even if the host environment has it set).
cmd.Env = filterEnv(os.Environ(), "CC_CONNECT_PERMISSION_HOOK_SKIP")
if err := cmd.Run(); err != nil {
return ccHookDecision{}, fmt.Errorf("hook exec: %w (stderr: %s)", err, truncateStr(strings.TrimSpace(stderr.String()), 200))
}
return parseHookOutput(stdout.Bytes())
}
// parseHookOutput parses hook stdout into a decision.
func parseHookOutput(data []byte) (ccHookDecision, error) {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 {
return ccHookDecision{}, nil // empty = ask/fallthrough
}
// Try plain text first: "allow", "deny", "ask".
text := strings.ToLower(string(trimmed))
switch text {
case "allow":
return ccHookDecision{Behavior: "allow"}, nil
case "deny":View on GitHub (pinned to 4000b2338a)
Solutions
- Read the stderr excerpt in the error message — it names the failing script and line; fix the hook script's bug
- Verify every binary the hook command invokes exists in PATH for the cc-connect process (`which <bin>` as the same user)
- If the error is 'signal: killed', the hook exceeded 60s — make the hook faster or remove blocking calls (e.g. network waits)
- Test the command standalone: `sh -c '<command>' < test-input.json` and confirm exit code 0 with valid JSON stdout
Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := exec.LookPath("sh"); err != nil { return err }
// dry-run the hook command before wiring it:
cmd := exec.Command("sh", "-c", command); cmd.Stdin = strings.NewReader(`{}`); if err := cmd.Run(); err != nil { return fmt.Errorf("hook dry-run failed: %w", err) } Try / catch
decision, err := runHookCommand(ctx, command, stdinData)
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
log.Errorf("hook exited %d; stderr: %s", exitErr.ExitCode(), exitErr.Stderr)
} else if ctx.Err() == context.DeadlineExceeded {
log.Error("hook exceeded 60s timeout")
}
return err
} Prevention
- Verify hook command binaries exist in the daemon's PATH, not just your interactive shell
- Keep hooks fast and non-blocking; never wait on stdin/network inside a permission hook
- Test hooks standalone with `sh -c '<command>'` before registering them
- Move hook diagnostics to stderr so they surface in the error's stderr excerpt
When it happens
Trigger: tryHook → runHookCommand → cmd.Run() returns an error: the command string references a binary not in PATH, the script exits non-zero (e.g. `deny` via exit code or a crash), or the 60s timeoutCtx kills it (signal: killed).
Common situations: User configured a hook command in Claude Code settings that references a script/interpreter missing on the cc-connect host; hook script has a bug and prints an error; hook script waits for input that never arrives and hits the 60s timeout; CC_CONNECT_PERMISSION_HOOK_SKIP filtering interacts with a hook that expects different env.
Understand the failure class
Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.
Related errors
- claudecode: start claude usage probe: %w
- acp: probe start %s: %w
- antigravitySession: start: %w
- marshal stdin: %w
- parse hook output: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/b0a1fc3b78989827.
Report an issue: GitHub.