{"record":{"id":"bf8d000828b7b7fc","repo":"sipeed/picoclaw","slug":"failed-to-encode-response","errorCode":null,"errorMessage":"Failed to encode response","messagePattern":"Failed to encode response","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/channels.go","lineNumber":81,"sourceCode":"func (h *Handler) handleGetChannelConfig(w http.ResponseWriter, r *http.Request) {\n\tchannelName := r.PathValue(\"name\")\n\titem, ok := findChannelCatalogItem(channelName)\n\tif !ok {\n\t\thttp.Error(w, \"Channel not found\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tcfg, err := config.LoadConfig(h.configPath)\n\tif err != nil {\n\t\thttp.Error(w, \"Failed to load config\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tresp := buildChannelConfigResponse(cfg, item)\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tif err := json.NewEncoder(w).Encode(resp); err != nil {\n\t\thttp.Error(w, \"Failed to encode response\", http.StatusInternalServerError)\n\t}\n}\n\nfunc findChannelCatalogItem(name string) (channelCatalogItem, bool) {\n\tfor _, item := range channelCatalog {\n\t\tif item.Name == name {\n\t\t\treturn item, true\n\t\t}\n\t}\n\treturn channelCatalogItem{}, false\n}\n\nvar channelSecretFieldMap = map[string][]string{\n\t\"weixin\":          {\"token\"},\n\t\"telegram\":        {\"token\"},\n\t\"discord\":         {\"token\"},\n\t\"slack\":           {\"bot_token\", \"app_token\"},\n\t\"feishu\":          {\"app_secret\", \"encrypt_key\", \"verification_token\"},","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/channels.go#L63-L99","documentation":"HTTP 500 from GET /api/channels/{name}/config: json.NewEncoder(w).Encode(resp) failed while streaming the already-built response. By this point the response struct was constructed successfully, so an encoding failure almost always means the client went away mid-write (connection reset, navigation, proxy timeout). A marshaling failure is only possible if a code change introduced a non-JSON-serializable field into the response.","triggerScenarios":"Client cancels the fetch (AbortController, tab close) exactly as the response streams; an intermediary (proxy/ALB) times out or resets the upstream connection; a change to buildChannelConfigResponse added a value encoding/json cannot marshal.","commonSituations":"Frontends aborting in-flight config fetches during rapid navigation; aggressive proxy idle timeouts; response struct gaining chan/func fields after a refactor.","solutions":["Correlate the timestamp with client/proxy logs — if the client aborted, it is benign noise","Check proxy access logs for upstream resets/timeouts and raise idle timeouts if needed","If reproducible with a stable client, inspect buildChannelConfigResponse output for non-marshalable types","Consider marshal-then-write so transport and encoding failures are distinguishable"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// server-side hardening: marshal first so encode errors are distinguishable from transport errors\nbuf, err := json.Marshal(resp)\nif err != nil {\n    http.Error(w, \"Failed to encode response\", http.StatusInternalServerError)\n    return\n}\nw.Header().Set(\"Content-Type\", \"application/json\")\n_, _ = w.Write(buf)","typeGuard":null,"tryCatchPattern":"Check the Encode error but classify it: after marshaling a plain struct, a write failure is a client disconnect — log at debug and move on; only a true marshal error (fixable struct) warrants a 500. Client-side, one cheap retry usually succeeds.","preventionTips":["Marshal-then-write so transport and encoding failures are distinguishable","Do not abort fetches whose results are still needed, or explicitly ignore abort errors","Keep response structs limited to JSON-serializable fields and smoke-test them"],"tags":["api","json","http","encoding","client-disconnect"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}