{"record":{"id":"e1a56ec9d35d765b","repo":"sipeed/picoclaw","slug":"decode-process-hook-q-s-result-w","errorCode":null,"errorMessage":"decode process hook %q %s result: %w","messagePattern":"decode process hook %q (.+?) result: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/agent/hook_process.go","lineNumber":365,"sourceCode":"\t\tmsg.Params = body\n\t}\n\n\tif err := ph.send(ctx, msg); err != nil {\n\t\tph.removePending(id)\n\t\treturn err\n\t}\n\n\tselect {\n\tcase resp, ok := <-respCh:\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"process hook %q closed while waiting for %s\", ph.name, method)\n\t\t}\n\t\tif resp.Error != nil {\n\t\t\treturn fmt.Errorf(\"process hook %q %s failed: %s\", ph.name, method, resp.Error.Message)\n\t\t}\n\t\tif out != nil && len(resp.Result) > 0 {\n\t\t\tif err := json.Unmarshal(resp.Result, out); err != nil {\n\t\t\t\treturn fmt.Errorf(\"decode process hook %q %s result: %w\", ph.name, method, err)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\tph.removePending(id)\n\t\treturn ctx.Err()\n\t}\n}\n\nfunc (ph *ProcessHook) send(ctx context.Context, msg processHookRPCMessage) error {\n\tbody, err := json.Marshal(msg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbody = append(body, '\\n')\n\n\tph.writeMu.Lock()\n\tdefer ph.writeMu.Unlock()","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/agent/hook_process.go#L347-L383","documentation":"Returned by ProcessHook.call when the hook did return a successful JSON-RPC result, but json.Unmarshal of resp.Result into the expected Go type failed. The hook's result payload does not match the schema the host expects for that method.","triggerScenarios":"A hook returning the wrong JSON shape: e.g. a string where a struct is expected, {\"approved\": \"yes\"} (string) instead of {\"approved\": true}, extra nesting, or numbers as strings. Fires only when `out != nil` and the result body is non-empty.","commonSituations":"Hand-written hooks that guess the response format; version mismatch between host and hook (schema changed); hooks written in dynamically typed languages (Node/Python) emitting loose types; null vs {} confusion.","solutions":["Make the hook return exactly the documented result shape for that method — correct types, no extra wrapping","Pin host and hook to matching versions so the result schema agrees","Reproduce locally: send the same request to the hook and inspect the raw JSON it emits","Watch for string/bool and string/number coercion mistakes in JS/Python hook implementations"],"exampleFix":"// hook response — before\n{\"result\": {\"approved\": \"yes\"}}\n\n// hook response — after\n{\"result\": {\"approved\": true}}","handlingStrategy":"validation","validationCode":"// Hook side: self-check the result shape against expected types before returning\nfunc assertResultShape(result any, expected string) error {\n    b, _ := json.Marshal(result)\n    var probe any\n    if err := json.Unmarshal(b, &probe); err != nil {\n        return err\n    }\n    _ = expected // compare keys/types against the documented schema here\n    return nil\n}","typeGuard":"func isHookResultDecodeError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"decode process hook\")\n}","tryCatchPattern":"err := ph.CallBeforeLLM(ctx, req, &out)\nif err != nil {\n    if isHookResultDecodeError(err) {\n        // hook returned a malformed result: skip hook rather than fail the turn\n        log.Printf(\"hook %T returned unparseable result: %v\", ph, err)\n    } else {\n        return err\n    }\n}","preventionTips":["Contract-test hook responses against the host's expected schema in CI","Use strict types in hook implementations (bool not string, no extra nesting)","Version the hook protocol and gate deploys on contract tests"],"tags":["hooks","rpc","json","schema","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}