Repository navigation
Description
If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.
Version Number: v9.4.94-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/test-runs/75704622/folder/53678307/48358512/1798307301?sort=%2Bidentifier
Email or phone of affected tester (no customers): applausetester+vq+2709@applause.expensifail.com
Issue reported by: Applause Internal Team
Bug source: Regression TC Execution
Device used: Windows 10/Chrome
App Component: Workspace Settings
Action Performed:
Preconditions Preconditions
The user is in a workspace with a connected bank account
The workspace has fewer than 8 members with admin role
- Navigate to the workspace from preconditions > Workflows
- Click on the Payer field to open the Authorized Payer RHP
- Set any of the admin as the Payer (Don't select owner) - Click Share
- Go to the Members tab
- Click on the Admin payer member to view the member detail page
- Verify that the role field is disabled and a hint text below it saying: "Role can't be changed because this member is a [payer] on this workspace.
Expected Result:
Verify that the role field is disabled and a hint text below it saying: "Role can't be changed because this member is a [payer] on this workspace.
Actual Result:
Missing hint and role not disabled for Authorized Payer
Workaround:
Unknown
Platforms:
- Android: App
- Android: mWeb Chrome
- iOS: App
- iOS: mWeb Safari
- iOS: mWeb Chrome
- Windows: Chrome
- MacOS: Chrome Safari
Screenshots/Videos
Bug7270566_1790493096450.missing_hint_role.mp4
Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7270566
Upwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022104469819161590429
- Upwork Job ID: 2104469819161590429
- Last Price Increase: 2026-09-28
Issue Owner
Current Issue Owner: @jasperhuanggMetadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
applause-bot commentedon Sep 28, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/9fd6c007d5ed6c118d414f783d87cdf2d4b14395c8f6d91e204a6b6911d4d878
MelvinBot commentedon Sep 28, 2026
Proposal
What is the root cause of that problem?
This is mostly intended behavior, and the test case is out of date. Allow promoting the Authorized Payer to Workspace Admin (merged 2026-08-27) deliberately removed the blanket lock and the "Role can't be changed…" hint. That PR fixed Authorized Payer cannot be promoted to Workspace Admin because the Role field is locked. A later revert, Revert #98123 to resolve Authorized Payer deploy blockers, was closed without merging.
The payer's Role row now stays editable when there is another payer role they can switch to. The role picker then offers only payer roles. As a result,
common.roleCannotBeChangedis no longer used anywhere.There is one real gap.
PAYER_ROLESincludes Editor, but the role picker never offers Editor. On a Collect workspace, an admin payer's Role row looks editable because of Editor. The picker then shows only "Workspace admin", the role they already have. So the row is a dead end, and the lock hint never shows.Code references
assignablePayerRoleshas a role different from the current one.hintText.PAYER_ROLESis every role with payments write access, which includes Editor.PAYER_ROLESand rejects roles that can't pay.What changes do you think we should make in order to solve the problem?
WorkspaceMemberDetailsPage, count only the payer roles the picker actually offers when you computecanReimburserChangeRole. In practice, leave out Editor.common.roleCannotBeChangedwith a link to the Workflows payments page. Bring back theuseEnvironmentURL andshouldRenderHintAsHTMLthat Allow promoting the Authorized Payer to Workspace Admin #98123 removed.What alternative solutions did you explore? (Optional)
If product wants the old blanket lock back, restore
!isReimburserincanEditSelectedMemberRoleand the hint. That reverts #98123, which the team already declined.Open question: I couldn't confirm which roles the backend accepts for the payer, especially Editor. That decides whether Editor should also be removed from
PAYER_ROLES.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102392)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Sep 28, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @getusha (
External)[-]Workflows - Missing hint and role not disabled for Authorized Payer[/-][+][$175] Workflows - Missing hint and role not disabled for Authorized Payer[/+]melvin-bot commentedon Sep 28, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022104469819161590429
naufalfx805-source commentedon Sep 28, 2026
naufalfx805-source commentedon Sep 28, 2026
melvin-bot commentedon Sep 28, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
bonnhatnguyen commentedon Sep 29, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In Workspace Settings > Members, when an Admin is designated as the Authorized Payer on a Collect workspace (or any workspace where no other payer role is assignable), the Role field remains interactive instead of disabled, and the expected hint text "Role can't be changed because this member is a [payer] on this workspace" is completely missing. Tapping on the row navigates to a dead-end role selection page that only offers the role they already hold (Workspace Admin).
What is the root cause of that problem?
There are two root causes introduced in PR #98123 (Allow promoting the Authorized Payer to Workspace Admin):
Inclusion of Non-UI Role
CONST.POLICY.ROLE.EDITORinPAYER_ROLES:In
src/libs/PolicyUtils.ts(lines 704–711):Because
EDITOR_POLICY_FEATUREShasWRITEaccess toWORKFLOWS_PAYMENTS,canRolePay(CONST.POLICY.ROLE.EDITOR)evaluates totrue, includingeditorinPAYER_ROLES.However,
editoris an internal permission bundle that is never offered inWorkspaceMemberRoleList.tsx.In
src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx(lines 160–162):For an Admin on a Collect policy,
canMemberAssignRole(policy, currentUserLogin, CONST.POLICY.ROLE.EDITOR)returnstrue(it is not inCONTROL_POLICY_ONLY_ROLES). Thus,assignablePayerRolescontains['admin', 'editor'].canReimburserChangeRoleevaluatesassignablePayerRoles.some((payerRole) => payerRole !== member?.role), which evaluates totruebecause'editor' !== 'admin'!This causes
canEditSelectedMemberRoleto evaluate totrueon Collect workspaces even though no other payer roles are actually available.Omission of
hintTextandshouldRenderHintAsHTMLfromMenuItemWithTopDescription:In PR Allow promoting the Authorized Payer to Workspace Admin #98123, the hint rendering on the role menu item was removed. Even when
canEditSelectedMemberRoleisfalse, the explanatory hint textcommon.roleCannotBeChanged("Role can't be changed because this member is a payer on this workspace.") is never rendered.What changes do you think we should make in order to solve the problem?
In
src/libs/PolicyUtils.ts:Filter out
CONST.POLICY.ROLE.EDITORfromPAYER_ROLESso that only user-facing selectable roles are considered:In
src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx:useEnvironment:Automated Unit Tests:
In
tests/ui/WorkspaceMemberDetailsPageTest.tsx:common.roleCannotBeChangedhint text containing the workflow payments URL.What alternative solutions did you explore? (Optional)
Payments Adminon Control workspaces without requiring unassigning them first. Our targeted fix preserves that capability while correctly handling the Collect workspace boundary.Contributor details
Your Expensify account email: bonnhatnguyen0@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0139ed4098bdc50182
melvin-bot commentedon Sep 29, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
21 remaining items
MelvinBot commentedon Nov 3, 2026
Proposal (updated against current
main)What is the root cause of that problem?
My earlier root cause still holds on current
main. The code moved, and the same bug now also affects a second screen.Allow promoting the Authorized Payer to Workspace Admin removed the blanket lock and the "Role can't be changed…" hint on purpose. The payer's Role row now stays editable when another payer role is available to them.
That check uses
PAYER_ROLES. This list includes Editor, but the role picker never offers Editor. On a Collect workspace, Editor makes an admin payer's row look editable. The picker then shows only "Workspace admin", the role they already have. So the row is a dead end, and no screen usescommon.roleCannotBeChanged.What changed since my first proposal:
PAYER_ROLESandcanRolePaymoved tosrc/libs/PolicyUtils/permissions.ts.getSelectableRoles(policy, login, allowedRoles). It returns exactly the roles the picker offers, so it already leaves out Editor.getSelectableRolesand works correctly.On a Control workspace, an admin payer can switch to Payments Admin. There, an editable row with no hint is intended, and the test case should be updated.
Code references
assignablePayerRolescomes fromPAYER_ROLES, so it includes Editor.hintText.PAYER_ROLEScontains every role with payments write access: Admin, Editor and Payments Admin.SELECTABLE_ROLESandgetSelectableRolesleave out Editor and Owner.What changes do you think we should make in order to solve the problem?
PolicyUtils, for examplecanReimburserChangeRole(policy, login, currentRole). It returns true whengetSelectableRoles(policy, login, [...PAYER_ROLES])contains a role other thancurrentRole.WorkspaceMemberDetailsPageand in the inline Role cell inWorkspaceMembersPage, in place of the existingassignablePayerRoleschecks.WorkspaceMemberDetailsPage, when the member is the payer and has no other role to switch to, disable the row. Showcommon.roleCannotBeChangedwithshouldRenderHintAsHTML, and link it to the Workflows payments tab withuseEnvironmentandROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID, CONST.TAB.WORKFLOWS.PAYMENTS).What alternative solutions did you explore? (Optional)
PAYER_ROLES. This is smaller, but it changesgetAllowedRolesForMemberand its unit test. It also hides that Editor can technically pay.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102392)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Nov 6, 2026
@getusha Whoops! This issue is 2 days overdue. Let's get this updated quick!
jeffrey-graham commentedon Nov 7, 2026
melvin-bot commentedon Nov 7, 2026
getusha commentedon Nov 9, 2026
@MelvinBot's proposal looks good to me
🎀 👀 🎀 C+ Reviewed
melvin-bot commentedon Nov 9, 2026
Triggered auto assignment to @jasperhuangg, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
MelvinBot commentedon Nov 9, 2026
Run details
jasperhuangg commentedon Nov 11, 2026
@getusha feel free to move forward with implementing Melvin's proposal.
getusha commentedon Nov 13, 2026
@MelvinBot implement this
MelvinBot commentedon Nov 13, 2026
🤖 Web test steps verification results: ❌ fail
Blocked on dev NewDot web: the existing session is suspended, and page snapshots contain zero nodes. No requested behavior could be verified.
Collect payer role lock, explanation, payer link, and inline Role cell
Not verified: session remained suspended after one retry, and the page exposed no content.Control admin payer role remains editable with Payments Admin and no lock hint
Not verified due to the same session blocker.Implemented in draft PR: Disable payer role editing when no selectable alternative exists, assigned to
getusha.Automated checks passed: 39,477 tests, lint, typecheck, spelling, formatting, targeted React Compiler checks, and Storybook smoke test. Manual web verification remains outstanding.
view run · no recording available