{"record":{"id":"288489f59b11c423","repo":"chenhg5/cc-connect","slug":"parse-hook-output-w","errorCode":null,"errorMessage":"parse hook output: %w","messagePattern":"parse hook output: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/claudecode/cc_hooks.go","lineNumber":299,"sourceCode":"\tcase \"allow\":\n\t\treturn ccHookDecision{Behavior: \"allow\"}, nil\n\tcase \"deny\":\n\t\treturn ccHookDecision{Behavior: \"deny\"}, nil\n\tcase \"ask\":\n\t\treturn ccHookDecision{}, nil\n\t}\n\n\t// Try structured JSON output.\n\tvar out struct {\n\t\tHookSpecificOutput struct {\n\t\t\tDecision struct {\n\t\t\t\tBehavior string `json:\"behavior\"`\n\t\t\t\tMessage  string `json:\"message\"`\n\t\t\t} `json:\"decision\"`\n\t\t} `json:\"hookSpecificOutput\"`\n\t}\n\tif err := json.Unmarshal(trimmed, &out); err != nil {\n\t\treturn ccHookDecision{}, fmt.Errorf(\"parse hook output: %w\", err)\n\t}\n\tbehavior := strings.ToLower(out.HookSpecificOutput.Decision.Behavior)\n\tif behavior == \"allow\" || behavior == \"deny\" {\n\t\treturn ccHookDecision{\n\t\t\tBehavior: behavior,\n\t\t\tMessage:  out.HookSpecificOutput.Decision.Message,\n\t\t}, nil\n\t}\n\treturn ccHookDecision{}, nil\n}\n\n// buildHookStdin constructs the JSON payload for the hook's stdin,\n// matching Claude Code's PermissionRequest hook input spec.\nfunc buildHookStdin(hctx hookContext) map[string]any {\n\tm := map[string]any{\n\t\t\"session_id\":      hctx.sessionID,\n\t\t\"hook_event_name\": \"PermissionRequest\",\n\t\t\"tool_name\":       hctx.toolName,","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/claudecode/cc_hooks.go#L281-L317","documentation":"parseHookOutput parses the permission hook's stdout JSON into ccHookDecision. This error wraps json.Unmarshal failure on the trimmed stdout, meaning the hook printed something that is not valid JSON (or a JSON shape differing from the expected hookSpecificOutput.decision wrapper).","triggerScenarios":"runHookCommand → parseHookOutput(stdout.Bytes()) fails when the hook command prints human-readable logs, empty output, or JSON without the expected {\"hookSpecificOutput\":{\"decision\":{\"behavior\":...}}} nesting, so Unmarshal errors or returns fields that don't fit.","commonSituations":"Hook script echoes debug text before/instead of the JSON decision; hook uses an older Claude Code hook output schema; hook prints nothing and the unmarshal fails on empty input; hook emits valid JSON but a different key layout.","solutions":["Fix the hook script so its final stdout line is exactly one JSON object with hookSpecificOutput.decision.behavior of \"allow\" or \"deny\"","Move any logging in the hook to stderr so stdout stays clean JSON","Check the hook's schema against Claude Code's current permission-hook output format and update keys","Run the hook manually and pipe stdout through `jq` to confirm it parses"],"exampleFix":"// before (hook script)\necho \"checking permission...\"\necho '{\"behavior\":\"allow\"}'\n// after\necho \"checking permission...\" >&2\necho '{\"hookSpecificOutput\":{\"decision\":{\"behavior\":\"allow\",\"message\":\"ok\"}}}'","handlingStrategy":"validation","validationCode":"out, _ := sh(\"-c\", hookCommand)\ntrimmed := strings.TrimSpace(out)\nif !json.Valid([]byte(trimmed)) { return fmt.Errorf(\"hook stdout is not JSON: %q\", trimmed) }","typeGuard":"func emitsHookJSON(cmd string) bool {\n    out, err := exec.Command(\"sh\", \"-c\", cmd).Output()\n    if err != nil { return false }\n    var v struct{ HookSpecificOutput struct{ Decision struct{ Behavior string `json:\"behavior\"` } `json:\"decision\"` } `json:\"hookSpecificOutput\"` }\n    return json.Unmarshal([]byte(strings.TrimSpace(string(out))), &v) == nil\n}","tryCatchPattern":"decision, err := parseHookOutput(stdout.Bytes())\nif err != nil {\n    log.Errorf(\"hook stdout not parseable: %v; raw=%q\", err, string(stdout.Bytes()))\n    return ccHookDecision{}, err\n}","preventionTips":["Hook scripts must print exactly one JSON object to stdout; send all logging to stderr","Keep hook output schema in sync with the Claude Code permission-hook spec","Verify with `sh -c '<hook>' | jq .` before deploying","Avoid echo/prints of debug text in hook scripts"],"tags":["json","parse","hooks"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}