{"record":{"id":"042080cffe667906","repo":"flipped-aurora/gin-vue-admin","slug":"result-msg","errorCode":null,"errorMessage":"result.Msg","messagePattern":"result\\.Msg","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/mcp/http_client.go","lineNumber":226,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"读取上游响应失败: %w\", err)\n\t}\n\n\tvar result upstreamEnvelope[T]\n\tif len(rawBody) > 0 {\n\t\tif err := json.Unmarshal(rawBody, &result); err != nil {\n\t\t\t// 上游返回非 JSON(如 404/502 的 HTML 或网关错误页):先暴露真实状态码,\n\t\t\t// 不让解析错误掩盖真实的 HTTP 失败\n\t\t\tif resp.StatusCode >= http.StatusBadRequest {\n\t\t\t\treturn nil, fmt.Errorf(\"上游请求失败，状态码: %d，响应: %s\", resp.StatusCode, truncateUpstreamBody(rawBody))\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"解析上游响应失败: %w\", err)\n\t\t}\n\t}\n\n\tif resp.StatusCode >= http.StatusBadRequest {\n\t\tif result.Msg != \"\" {\n\t\t\treturn nil, errors.New(result.Msg)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"上游请求失败，状态码: %d\", resp.StatusCode)\n\t}\n\n\tif result.Code != 0 {\n\t\tif result.Msg != \"\" {\n\t\t\treturn nil, errors.New(result.Msg)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"上游请求失败，业务码: %d\", result.Code)\n\t}\n\n\treturn &result, nil\n}\n\n// doUpstreamRaw 是动态 tool 专用的上游调用：接受完整 path（已替换路径参数）、method、query、body，\n// 返回原始响应字节（动态 tool 把响应原样包成 MCP text content 返回给外部 AI）。\n// 认证头从 ctx 取（与 doUpstream 一致，由外部 AI 通过 MCP 请求头透传 token）。\nfunc doUpstreamRaw(ctx context.Context, method, path string, query url.Values, body any) (int, []byte, error) {","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/mcp/http_client.go#L208-L244","documentation":"In doUpstream, when the upstream HTTP response status is >= 400 and the decoded response body carries a non-empty result.Msg, that message is surfaced verbatim as the error via errors.New(result.Msg). The literal string 'result.Msg' in an error log means the upstream returned an error status but the Msg field was empty or the message content itself is this placeholder — i.e. the real upstream failure text was lost.","triggerScenarios":"Any getUpstream/postUpstream/deleteUpstream call where the upstream replies with status >= 400 and result.Msg is empty, causing the placeholder to be recorded; or an upstream explicitly sending Msg == \"result.Msg\".","commonSituations":"Upstream service returning a bare error status (404/500) with an empty or non-standard body; response shape mismatch so JSON decoding fills nothing into Msg; gateway/proxy intercepting the request and returning a non-{code,msg} body.","solutions":["Log the full upstream status code and raw body alongside this error to see the real failure reason.","Check the upstream service is running and the URL/path in the MCP config is correct.","Verify the upstream response follows the {code, data, msg} convention; if not, fix the server or the decoder.","Improve the fallback: include StatusCode in the error even when Msg is empty (e.g. fmt.Errorf(\"%s (HTTP %d)\", result.Msg, resp.StatusCode))."],"exampleFix":"// before\nreturn nil, errors.New(result.Msg)\n// after\nif result.Msg != \"\" {\n    return nil, fmt.Errorf(\"%s (HTTP %d)\", result.Msg, resp.StatusCode)\n}\nreturn nil, fmt.Errorf(\"上游请求失败，状态码: %d\", resp.StatusCode)","handlingStrategy":"try-catch","validationCode":"// pre-flight: verify upstream reachable before batch work\nresp, err := http.Get(upstreamHealthURL)\nif err != nil || resp.StatusCode >= 400 {\n    return errors.New(\"upstream service unavailable; aborting MCP call\")\n}","typeGuard":null,"tryCatchPattern":"resp, err := client.postUpstream(url, payload)\nif err != nil {\n    var statusErr interface{ StatusCode() int }\n    // log status + raw body for diagnosis since Msg may be empty\n    log.Printf(\"upstream failed: %v\", err)\n    return fmt.Errorf(\"upstream call failed: %w\", err)\n}","preventionTips":["Monitor upstream service health and configure retries/backoff for transient 5xx.","Ensure upstream endpoints return the {code, data, msg} envelope with a populated msg on failure.","Log the HTTP status and raw body whenever doUpstream returns an error to recover the lost context."],"tags":["http","upstream","network","go","error-mapping"],"backgroundTag":"upstream-http-error","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}