sipeed/picoclaw · error
decode process hook %q %s result: %w
Error message
decode process hook %q %s result: %w
What it means
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.
Source
Thrown at pkg/agent/hook_process.go:365
msg.Params = body
}
if err := ph.send(ctx, msg); err != nil {
ph.removePending(id)
return err
}
select {
case resp, ok := <-respCh:
if !ok {
return fmt.Errorf("process hook %q closed while waiting for %s", ph.name, method)
}
if resp.Error != nil {
return fmt.Errorf("process hook %q %s failed: %s", ph.name, method, resp.Error.Message)
}
if out != nil && len(resp.Result) > 0 {
if err := json.Unmarshal(resp.Result, out); err != nil {
return fmt.Errorf("decode process hook %q %s result: %w", ph.name, method, err)
}
}
return nil
case <-ctx.Done():
ph.removePending(id)
return ctx.Err()
}
}
func (ph *ProcessHook) send(ctx context.Context, msg processHookRPCMessage) error {
body, err := json.Marshal(msg)
if err != nil {
return err
}
body = append(body, '\n')
ph.writeMu.Lock()
defer ph.writeMu.Unlock()View on GitHub (pinned to 49183d7e8d)
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
Example fix
// hook response — before
{"result": {"approved": "yes"}}
// hook response — after
{"result": {"approved": true}} Defensive patterns
Strategy: validation
Validate before calling
// Hook side: self-check the result shape against expected types before returning
func assertResultShape(result any, expected string) error {
b, _ := json.Marshal(result)
var probe any
if err := json.Unmarshal(b, &probe); err != nil {
return err
}
_ = expected // compare keys/types against the documented schema here
return nil
} Type guard
func isHookResultDecodeError(err error) bool {
return err != nil && strings.Contains(err.Error(), "decode process hook")
} Try / catch
err := ph.CallBeforeLLM(ctx, req, &out)
if err != nil {
if isHookResultDecodeError(err) {
// hook returned a malformed result: skip hook rather than fail the turn
log.Printf("hook %T returned unparseable result: %v", ph, err)
} else {
return err
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- process hook %q is closed
- process hook %q closed while waiting for %s
- process hook %q %s failed: %s
- write process hook %q message: %w
- decode config %s: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/e1a56ec9d35d765b.
Report an issue: GitHub.