{"record":{"id":"487c4ba99ad621ba","repo":"flipped-aurora/gin-vue-admin","slug":"w-487c4b","errorCode":null,"errorMessage":"获取角色当前菜单授权失败: %w","messagePattern":"获取角色当前菜单授权失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/mcp/role_menu_assigner.go","lineNumber":92,"sourceCode":"\tallResp, err := postUpstream[[]system.SysBaseMenu](ctx, \"/menu/getMenuList\", map[string]any{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"获取菜单列表失败: %w\", err)\n\t}\n\tmenuIndex := make(map[uint]system.SysBaseMenu)\n\tflattenBaseMenus(allResp.Data, menuIndex)\n\n\tfor _, id := range menuIDs {\n\t\tif _, ok := menuIndex[id]; !ok {\n\t\t\treturn nil, fmt.Errorf(\"菜单 %d 不存在,可先用 list_all_menus 查询全部菜单\", id)\n\t\t}\n\t}\n\n\t// 角色当前授权(先读,防覆盖丢权)\n\tcurrentResp, err := postUpstream[map[string][]system.SysMenu](ctx, \"/menu/getMenuAuthority\", map[string]any{\n\t\t\"authorityId\": authorityID,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"获取角色当前菜单授权失败: %w\", err)\n\t}\n\tcurrentIDs := make([]uint, 0)\n\tfor _, menu := range currentResp.Data[\"menus\"] {\n\t\tid := menu.MenuId\n\t\tif id == 0 {\n\t\t\tid = menu.ID\n\t\t}\n\t\tif id != 0 && !containsUint(currentIDs, id) {\n\t\t\tcurrentIDs = append(currentIDs, id)\n\t\t}\n\t}\n\n\t// 请求集 = 传入菜单 ∪ 各自的完整父链\n\trequested := make([]uint, 0, len(menuIDs))\n\tparentAdded := make([]uint, 0)\n\tfor _, id := range menuIDs {\n\t\trequested = append(requested, id)\n\t\tparentID := menuIndex[id].ParentId","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/mcp/role_menu_assigner.go#L74-L110","documentation":"This error wraps a failure when the role-menu assignment tool calls the upstream admin API POST /menu/getMenuAuthority to read a role's current menu authorization before modifying it. The pre-read exists to prevent accidentally overwriting/losing existing permissions when writing back a merged menu set. Any network, auth, or upstream 5xx failure surfaces here wrapped with %w.","triggerScenarios":"Calling the role-menu-assigner MCP Handle() with an authorityId when the HTTP POST to /menu/getMenuAuthority fails: upstream server unreachable, JWT/token invalid, upstream returns non-2xx, request context canceled, or response body cannot be decoded into {menus: []SysMenu}.","commonSituations":"Running the MCP tool while the gin-vue-admin backend is down or restarting; expired JWT in the MCP HTTP client config; wrong upstream base URL/port; authorityId referencing a role that makes upstream return an error; firewall/proxy blocking the internal call.","solutions":["Verify the upstream gin-vue-admin server is running and reachable at the configured base URL (curl the endpoint).","Check/refresh the JWT token used by the MCP HTTP client (server/mcp/http_client.go) in its config.","Confirm the upstream route /menu/getMenuAuthority exists and the caller role has Casbin permission for it.","Inspect the wrapped cause (%w) in the error chain for the concrete failure (timeout, 401, 500, decode error).","Retry once transient failures (network blips) resolve after the backend is healthy."],"exampleFix":"// before\nresp, err := postUpstream[map[string][]system.SysMenu](ctx, \"/menu/getMenuAuthority\", body)\n// after\nctx, cancel := context.WithTimeout(ctx, 10*time.Second)\ndefer cancel()\nresp, err := postUpstream[map[string][]system.SysMenu](ctx, \"/menu/getMenuAuthority\", body)\nif err != nil {\n    log.Printf(\"getMenuAuthority failed: %v\", err) // inspect wrapped cause\n    return nil, err\n}","handlingStrategy":"retry","validationCode":"// pre-check upstream reachability before invoking the assigner\nresp, err := http.Get(baseURL + \"/health\")\nif err != nil || resp.StatusCode != 200 {\n    return fmt.Errorf(\"upstream admin server unreachable, aborting menu assignment\")\n}","typeGuard":null,"tryCatchPattern":"result, err := assigner.Handle(ctx, args)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() {\n        // transient: retry with backoff\n    } else if strings.Contains(err.Error(), \"401\") {\n        // refresh JWT then retry once\n    }\n    return err\n}","preventionTips":["Health-check the upstream server before running menu assignments.","Keep the MCP client JWT refreshed; expire it intentionally in staging to test handling.","Pin the upstream base URL in config and validate it at startup.","Pass a context with a sensible timeout (5-10s) to Handle."],"tags":["http","upstream-api","mcp","wrapped-error"],"backgroundTag":"upstream-http-request-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}