{"record":{"id":"9e08cee56d16ccc1","repo":"chenhg5/cc-connect","slug":"pisession-marshal-extension-ui-response-w","errorCode":null,"errorMessage":"piSession: marshal extension_ui_response: %w","messagePattern":"piSession: marshal extension_ui_response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/pi/session.go","lineNumber":1192,"sourceCode":"\tcase \"confirm\":\n\t\t// extension_confirm goes through the regular permission flow, not the\n\t\t// AskUserQuestion flow, so the engine sends plain \"allow\"/\"deny\"\n\t\t// PermissionResults (no UpdatedInput.answers). Forward as the default\n\t\t// case: confirmed=Behavior==\"allow\", else confirmed=false (pgate's\n\t\t// ctx.ui.confirm resolves false for both \"deny\" and \"no confirmation\",\n\t\t// which is what we want).\n\t\tfallthrough\n\tdefault:\n\t\tresp = map[string]any{\n\t\t\t\"type\":      \"extension_ui_response\",\n\t\t\t\"id\":        extID,\n\t\t\t\"confirmed\": result.Behavior == \"allow\",\n\t\t}\n\t}\n\n\tb, err := json.Marshal(resp)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"piSession: marshal extension_ui_response: %w\", err)\n\t}\n\tb = append(b, '\\n')\n\n\tslog.Debug(\"piSession: sending extension_ui_response\", \"id\", extID, \"behavior\", result.Behavior)\n\ts.rpcStdinMu.Lock()\n\t_, err = s.rpcStdin.Write(b)\n\ts.rpcStdinMu.Unlock()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"piSession: write extension_ui_response: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// ── AgentSession interface ──────────────────────────────────\n\nfunc (s *piSession) Events() <-chan core.Event {\n\treturn s.events","sourceCodeStart":1174,"sourceCodeEnd":1210,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/pi/session.go#L1174-L1210","documentation":"RespondPermission in agent/pi/session.go fails when it cannot JSON-marshal the extension_ui_response payload it is about to send back to pi. Because the response struct is built from simple fields (id, behavior), a marshal failure almost always indicates a programming error such as an unsupported value type (e.g. a channel or func accidentally placed in the options map).","triggerScenarios":"Calling RespondPermission on a pi RPC session where the constructed resp map/struct contains a value json.Marshal cannot encode; effectively only reachable via code changes adding non-serializable fields.","commonSituations":"A developer extended the permission-response payload with a non-JSON-encodable type; custom struct without json tags holding unsupported types.","solutions":["Inspect the wrapped json error to find the offending field/type","Ensure every value in the response is a JSON-safe type (string, bool, number, map, slice)","Add json tags to any custom struct fields","Add a unit test covering RespondPermission payload marshalling"],"exampleFix":"// before\nresp := map[string]any{\"id\": extID, \"callback\": result.Callback}\nb, err := json.Marshal(resp)\n// after\nresp := map[string]any{\"id\": extID, \"confirmed\": result.Behavior == \"allow\"}\nb, err := json.Marshal(resp) // only JSON-safe values","handlingStrategy":"validation","validationCode":"if _, err := json.Marshal(resp); err != nil {\n    slog.Error(\"permission response not serializable\", \"err\", err)\n}","typeGuard":"func jsonSafe(v any) bool {\n    _, err := json.Marshal(v)\n    return err == nil\n}","tryCatchPattern":"if err := sess.RespondPermission(ctx, id, result); err != nil {\n    if strings.Contains(err.Error(), \"marshal\") {\n        slog.Error(\"bad permission payload\", \"err\", err) // code bug, do not retry\n    }\n    return err\n}","preventionTips":["Only put JSON-safe types in the response map","Add json tags to custom structs","Unit-test RespondPermission payload marshalling","Treat marshal errors as programming bugs, not transient failures"],"tags":["go","json","serialization"],"backgroundTag":"json-marshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}