{"record":{"id":"dd621e7c78546782","repo":"antiwork/gumroad","slug":"something-went-wrong-dd621e","errorCode":null,"errorMessage":"Something went wrong.","messagePattern":"Something went wrong\\.","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"app/javascript/data/settings/team.ts","lineNumber":53,"sourceCode":"  });\n  if (response.ok) {\n    return typia.assert<{ success: false; error_message: string } | { success: true }>(await response.json());\n  }\n  return { success: false, error_message: \"Sorry, something went wrong. Please try again.\" };\n};\n\nexport const updateMember = async (memberInfo: MemberInfo, role: Role) => {\n  const requestInfo =\n    memberInfo.type === \"invitation\"\n      ? { url: Routes.settings_team_invitation_path(memberInfo.id, \"json\"), data: { team_invitation: { role } } }\n      : { url: Routes.settings_team_member_path(memberInfo.id, \"json\"), data: { team_membership: { role } } };\n  const response = await request({\n    method: \"PUT\",\n    accept: \"json\",\n    ...requestInfo,\n  });\n\n  if (!response.ok) throw new ResponseError();\n};\n\nexport const deleteMember = async (memberInfo: MemberInfo) => {\n  const url =\n    memberInfo.type === \"invitation\"\n      ? Routes.settings_team_invitation_path(memberInfo.id, \"json\")\n      : Routes.settings_team_member_path(memberInfo.id, \"json\");\n  const response = await request({\n    method: \"DELETE\",\n    accept: \"json\",\n    url,\n  });\n\n  if (!response.ok) throw new ResponseError();\n};\n\nexport const resendInvitation = async (memberInfo: MemberInfo) => {\n  const response = await request({","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/data/settings/team.ts#L35-L71","documentation":"updateMember PUTs a role change to Routes.settings_team_invitation_path or Routes.settings_team_member_path (app/javascript/data/settings/team.ts:42-54) and throws ResponseError with the default message 'Something went wrong.' when response.ok is false. ResponseError (app/javascript/utils/request.ts:27) is the app-wide fetch-wrapper error. Because request() already converts 5xx, 429 (RateLimitError), and network failures, this throw means a 4xx reached the handler: 401 (expired session), 403 (acting seller not allowed to manage roles), 404 (stale member/invitation id), or 422 (role not accepted).","triggerScenarios":"Changing a team member's role from the Select in Settings > Team (pages/Settings/Team/Show.tsx:249 calls updateMember) when: the acting seller is not the team owner (403); the invitation was accepted or revoked in another tab so memberInfo.id no longer resolves (404); the role sent is not one of ROLES (422); or the page sat open past session expiry so the JSON PUT returns 401.","commonSituations":"Team settings page left open overnight (session expired); two owners editing the team concurrently so one acts on a deleted/changed member; tests stubbing these endpoints with non-2xx; an admin-role account (not owner) attempting role changes it is not permitted to make.","solutions":["Reload Settings > Team so memberInfo comes from a fresh list, then retry the role change","Verify the acting account is the team owner — role management is owner-gated in the settings team controllers","Confirm memberInfo.id still exists and memberInfo.type picks the right route at team.ts:44-46 (invitation vs membership)","Check the browser network tab / Rails log for the exact 4xx status on the PUT before assuming a frontend bug","Catch ResponseError and surface e.message as Show.tsx:259-262 already does, so the failure is never silent"],"exampleFix":"// before\nif (!response.ok) throw new ResponseError();\n\n// after — surface the server's reason, mirroring wishlists.ts:104-107\nif (!response.ok) {\n  const body: unknown = await response.json().catch(() => null);\n  const message = typia.is<{ error: string }>(body) ? body.error : \"Could not change this member's role.\";\n  throw new ResponseError(message);\n}","handlingStrategy":"try-catch","validationCode":"const canChangeRole = (m: MemberInfo, role: Role) =>\n  ROLES.includes(role) && m.id !== \"\" && m.role !== role; // skip no-op / invalid PUTs (Show.tsx:248 checks the first two)\nif (!canChangeRole(memberInfo, nextRole)) return;","typeGuard":"import { ResponseError } from \"$app/utils/request\";\nconst isResponseError = (e: unknown): e is ResponseError => e instanceof ResponseError;","tryCatchPattern":"import { assertResponseError } from \"$app/utils/request\";\ntry {\n  await updateMember(memberInfo, role);\n} catch (e) {\n  assertResponseError(e); // rethrows non-ResponseError (e.g. typia failures) instead of swallowing\n  showAlert(e.message, \"error\");\n}","preventionTips":["Only build the role Select's options from ROLES the server accepts","Disable the control while a change is in flight (Show.tsx tracks loading)","Refresh member data before acting when the page has been open a long time"],"tags":["http","fetch","team-management","ruby-on-rails","typescript"],"backgroundTag":"http-403-forbidden","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}