You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$250] Travel - “Something went wrong” error when completing Travel setup #91402
1.Open a workspace with Travel enabled
2.Navigate to Workspace settings
3.Open the Travel setup flow
4.Enter the required Travel setup information
.5Click Continue
Expected Result:
The Travel setup completes successfully and the user proceeds to the next Travel configuration step.
Actual Result:
A “Something went wrong, please try again later” error is displayed after clicking Continue, preventing completion of the Travel setup flow.
The "Something went wrong. Please try again later." error is a generic catch-all displayed when the AcceptSpotnanaTerms API call fails. The frontend error-handling path is:
When the API returns a non-200 response (or a network error), the failureData fires, setting TRAVEL_PROVISIONING.errors to the travel.errorMessage translation key
The frontend code itself is correct — it properly surfaces backend failures. This appears to be a backend/internal issue. A separate internal investigation is needed to determine why the AcceptSpotnanaTerms endpoint is returning errors (e.g., Spotnana provisioning failure, authentication issue, or a recent backend change).
What changes do you think we should make in order to solve the problem?
The backend AcceptSpotnanaTerms handler needs investigation. The frontend error path is working as designed — it correctly shows the generic error when the API call fails. The fix will be on the backend side, depending on what the server-side logs reveal about why provisioning is failing.
If the backend is returning a specific error that could be surfaced more helpfully (rather than the generic "Something went wrong"), the frontend could be updated to handle additional error codes in the .then() handler at DynamicTravelTerms.tsx:84-137, similar to how permissionDenied and additionalVerificationRequired are already handled.
What alternative solutions did you explore? (Optional)
Reviewed recent frontend changes for potential regressions:
PR migrate travel part 1 #89727 (2026-05-14): Migrated Travel pages to dynamic routes — could theoretically affect parameter passing, but the domain and policyID are correctly extracted from route.params at DynamicTravelTerms.tsx:60-61
None of these frontend changes explain the error — the API parameters are correctly constructed and sent. The root cause is the backend returning a failure response.
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.
🚨 Edited by proposal-police: This proposal was edited at 2026-05-22 09:24:31 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
After completing the Travel setup (entering the company address and continuing to the Terms screen), the user sees "Something went wrong, please try again later" even though provisioning should succeed, blocking them from completing Travel setup.
What is the root cause of that problem?
The TRAVEL_PROVISIONING Onyx key persists across Travel setup attempts and WorkspaceAddressForTravelPage does not clear it before navigating forward. If the user previously hit any error during a Travel setup attempt (timed-out OTP, an AcceptSpotnanaTerms retry, navigating away mid-flow, etc.), failureData from acceptSpotnanaTerms left TRAVEL_PROVISIONING.errors populated with the travel.errorMessage translation key ("Something went wrong, please try again later."). When the user re-enters the flow, fills the address, and the page navigates to TCS with forceReplace: true, the stale errors is still in Onyx. DynamicTravelTerms then immediately renders that stale error via FormAlertWithSubmitButton because of the selector travelProvisioning?.errors && !travelProvisioning?.error — so the user sees the failure message before any new API call has even been made.
BookTravelButton.navigateToAcceptTerms and DynamicDomainSelectorPage.provisionTravelForDomain both call cleanupTravelProvisioningSession() before navigating to TCS exactly to avoid this, but the address-completion path skips it:
What changes do you think we should make in order to solve the problem?
Call cleanupTravelProvisioningSession() inside updatePolicyAddress in WorkspaceAddressForTravelPage.tsx just before navigating to the TCS route, mirroring what the sibling entry points (BookTravelButton.navigateToAcceptTerms and DynamicDomainSelectorPage) already do. This ensures any stale errors/error/isLoading state from a prior provisioning attempt is wiped before the user lands on the Terms screen, so the screen starts in a clean state and the "Something went wrong" message only appears if the new AcceptSpotnanaTerms call actually fails.
🚨 Edited by proposal-police: This proposal was edited at 2026-06-18 14:40:35 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
When an admin completes the workspace Travel setup flow — enters the company address, accepts the Terms & Conditions, and clicks Continue — the app shows the generic "Something went wrong, please try again later." and provisioning never completes. Per the maintainer's backend logs, the underlying server failure is INVALID_USER_GROUP_MEMBER_UPDATE.
What is the root cause of that problem?
There are two layers, and they must not be conflated:
Layer 1 — the visible string is a faithful, but lossy, surfacing of a backend failure. "Something went wrong, please try again later." is the travel.errorMessage key written by the failureData of acceptSpotnanaTerms into TRAVEL_PROVISIONING.errors:
DynamicTravelTerms reads it back and renders it in the FormAlertWithSubmitButton; the generic jsonCode !== 200 branch rejects without surfacing the server's real message, and the trailing .catch(() => {}) discards the rejection entirely:
// Errors are surfaced via TRAVEL_PROVISIONING in Onyx
});
So every non-200 from AcceptSpotnanaTerms that isn't permissionDenied / additionalVerificationRequired collapses into the same opaque string — which is exactly why this regression has been hard to action (the C+ reviewer and maintainer both had to fall back to reading raw BE logs).
Layer 2 — a frontend ordering race feeds incomplete state into provisioning. For a workspace with no persisted policy.address, BookTravelButton routes to the address screen because Spotnana requires an address to create the policy entity:
// Determine the domain to associate with the workspace during provisioning in Spotnana.
// - If all admins share the same private domain, the workspace is tied to it automatically.
// - If admins have multiple private domains, the user must select one.
// - Public domains are not allowed; an error page is shown in that case.
elseif(adminDomains.length===1){
WorkspaceAddressForTravelPage calls updateAddress(...) and synchronously replaces the route with the TCS screen — no await, no pending-state observation, no cleanupTravelProvisioningSession():
updateAddress issues UPDATE_POLICY_ADDRESS via API.write, a sequential-queue command whose Promise resolves on enqueue, not on server completion. Meanwhile acceptSpotnanaTerms fires ACCEPT_SPOTNANA_TERMS via API.makeRequestWithSideEffects, which bypasses the queue and hits the server immediately:
The optimistic address + pendingFields.address: UPDATE write makes the address look set on the client, but the server may not have persisted it yet when ACCEPT_SPOTNANA_TERMS arrives:
The "Windows/Chrome only" repro is consistent with a timing-sensitive race — a fast machine reaches Continue before UPDATE_POLICY_ADDRESS drains.
Bridging Layer 2 to the observed INVALID_USER_GROUP_MEMBER_UPDATE (hypothesis, needs BE correlation). Spotnana provisioning creates the company/domain entity and then attaches the admin to that entity's user group. If ACCEPT_SPOTNANA_TERMS runs before the address is persisted, the entity is created without a valid address (or not fully created), so the downstream "add admin to the entity's user group" step operates against an incomplete/invalid group → INVALID_USER_GROUP_MEMBER_UPDATE. This plausibly makes the user-group error a downstream symptom of the same race, not an unrelated bug. I want to be explicit that this is a hypothesis: rlinoz read the logs and said "not sure why yet," so the bridge must be confirmed by correlating the BE error timestamp against whether UPDATE_POLICY_ADDRESS had committed first. If the BE error reproduces even when the address is provably persisted first, then it is an independent backend defect and Layer 2's FE fix is purely a robustness improvement.
What changes do you think we should make in order to solve the problem?
1. (FE) Remove the race at the only screen that introduces it — scoped, minimum blast radius. In WorkspaceAddressForTravelPage, do not navigate to TCS synchronously after updateAddress(...). Instead set a local "pending submit" flag and observe policy.pendingFields?.address: updateAddress already toggles it to UPDATE in optimisticData and clears it to null in finallyData (Policy.ts#L2229-L2253). When it flips back to null, the write has returned from the server — only then navigate to TCS. Show the address submit button's standard isLoading spinner while waiting, and call cleanupTravelProvisioningSession() before navigating (mirroring the canonical entry point BookTravelButton.navigateToAcceptTerms) so a stale TRAVEL_PROVISIONING.errors can't re-render on TCS mount.
This keeps updateAddress's API.write semantics intact for every other caller (Workspace Profile, NetSuite onboarding, offline edits) and keeps acceptSpotnanaTerms as a side-effect request (the caller still needs its response to open Travel).
2. (FE) Make the failure diagnosable so this never becomes an opaque "Something went wrong" again — worth doing regardless of Layer 2's outcome. In the generic non-200 branch of acceptTermsAndOpenTravelDot, surface the backend's real message and stop swallowing the rejection:
// Handle general API failure
if (response?.jsonCode !== 200) {
+ Log.warn('[Travel] AcceptSpotnanaTerms failed', {jsonCode: response?.jsonCode, message: response?.message});+ if (response?.message) {+ Onyx.merge(ONYXKEYS.TRAVEL_PROVISIONING, {errors: getMicroSecondOnyxErrorWithMessage(response.message)});+ }
return Promise.reject(new Error('Request failed'));
}
- .catch(() => {- // Errors are surfaced via TRAVEL_PROVISIONING in Onyx- });+ .catch((error) => {+ Log.warn('[Travel] acceptTermsAndOpenTravelDot rejected', {message: error?.message});+ });
3. (BE) Investigate INVALID_USER_GROUP_MEMBER_UPDATE directly. The maintainer's log is the authoritative signal here. The BE owner should confirm whether the error disappears once the policy address is committed before ACCEPT_SPOTNANA_TERMS (validating the bridge), and whether the provisioning step should be idempotent / send the address alongside the terms-accept as a fallback (@shubham1206agra's suggestion). Step 1 likely removes the FE-triggerable path; step 3 closes it for any non-FE ordering.
Tests:
Unit test on WorkspaceAddressForTravelPage: submitting the address sets the pending state and does not navigate to TCS until policy.pendingFields.address clears to null.
Flow test for a Travel-enabled workspace with no persisted address: submit address → assert navigation to TCS only after the address write settles, and that cleanupTravelProvisioningSession() ran.
What alternative solutions did you explore? (Optional)
API.waitForWrites(ACCEPT_SPOTNANA_TERMS) inside acceptSpotnanaTerms (@KJ21-ENG/@MobileMage). Correct diagnosis of the race, but too coarse: waitForWrites drains the entire sequential queue (chat messages, unrelated policy edits), so Continue hangs on anything unrelated that is retrying, and the penalty hits every call site of acceptSpotnanaTerms including BookTravelButton.navigateToAcceptTerms where there is no address write to wait on. Observing pendingFields.address ties the wait to the one write that matters.
await the updateAddress Promise before navigating (@abbasifaizan70). Does not fix the race — API.write resolves on enqueue, not server completion, so ACCEPT_SPOTNANA_TERMS still fires before UPDATE_POLICY_ADDRESS commits. Making it actually wait would require converting UPDATE_POLICY_ADDRESS to a side-effect request, regressing offline persistence for every other caller.
Convert updateAddress itself to a side-effect request. Same regression — it changes the semantics of a normal persisted policy write for all surfaces, just to fix one setup flow.
Treat this as purely a backend bug and do nothing on FE. Rejected: even if the BE handler is fully fixed, the synchronous navigate-before-write-settles pattern remains a latent ordering hazard, and the opaque generic error keeps future Travel failures undebuggable. Steps 1–2 are defensible on their own; step 3 is the actual provisioning fix.
🚨 Edited by proposal-police: This proposal was edited at 2026-05-22 10:23:47 UTC.
Replaces my earlier proposal: I originally focused on the permission-info dynamic route. That only affects the ERROR_PERMISSION_DENIED branch; the failing setup path here is the address-write/terms-accept race below.
Proposal
Please re-state the problem that we are trying to solve in this issue.
Completing the workspace Travel setup flow can show "Something went wrong, please try again later" after the admin enters the required company address and clicks Continue on the Travel terms step. That blocks the workspace from finishing Travel provisioning.
What is the root cause of that problem?
The address step can navigate to the terms step before the required company address has actually been saved on the backend.
For an eligible workspace with a single private admin domain and no policy address, BookTravelButton sends the user to the workspace address screen because Spotnana requires an address when creating the policy entity:
API.write only pushes the request to the sequential queue; it does not mean the backend has already processed the write. By contrast, AcceptSpotnanaTerms is sent through API.makeRequestWithSideEffects, which processes immediately and returns the response needed to open Travel:
Log.info(`[API] '${command}' is waiting on ${getPersistedRequestsLength()} write commands`);
}
returnwaitForSequentialQueueIdle();
So the admin can click Continue on the terms page while UPDATE_POLICY_ADDRESS is still pending in the write queue. The backend then attempts Spotnana provisioning for a policy that still has no persisted address and returns a failure. The current failureData maps that failure to travel.errorMessage, which is the generic "Something went wrong" message shown in this issue.
What changes do you think we should make in order to solve the problem?
Make acceptSpotnanaTerms() wait for queued writes before it sends AcceptSpotnanaTerms.
Concretely:
In src/libs/actions/Travel.ts, call the existing API.waitForWrites(SIDE_EFFECT_REQUEST_COMMANDS.ACCEPT_SPOTNANA_TERMS) before API.makeRequestWithSideEffects(...).
Keep AcceptSpotnanaTerms as a side-effect request after the wait, because the caller still needs the response data to handle success, permission-denied, and additional-verification outcomes.
If we want the button to show loading while the write queue drains, apply only the Travel provisioning loading optimistic update before the wait and do not pass the same optimistic update again to the side-effect request.
Add a focused unit test around acceptSpotnanaTerms() that mocks API.waitForWrites and API.makeRequestWithSideEffects, then asserts AcceptSpotnanaTerms is not sent until the write wait resolves.
Add or update a flow-level test for a paid Travel-enabled workspace with one private admin domain and no policy address: submit the workspace address, continue on terms, and verify the app drains UpdatePolicyAddress before sending AcceptSpotnanaTerms.
This touches the Travel provisioning action boundary only. It does not change normal policy address editing outside Travel, and it does not change the success/error routing inside DynamicTravelTerms; it only guarantees the side-effect provisioning command does not run against stale policy data.
What alternative solutions did you explore? (Optional)
Waiting in WorkspaceAddressForTravelPage before navigating to the terms page would also address this specific entry path, but it puts the ordering guarantee on one screen instead of on the command that actually depends on queued writes. Putting the wait inside acceptSpotnanaTerms() protects every terms-entry path that depends on recent policy writes.
Changing UpdatePolicyAddress to a side-effect request would make the address screen wait for a direct response, but that changes the semantics of a normal persisted policy write just for this Travel flow. The existing write queue already has waitForWrites() for this exact "do not run this command until writes settle" case.
Sending the address again with AcceptSpotnanaTerms, or making the backend tolerate a missing stored address, would also work as a backend fallback. The frontend still should preserve request ordering because the app already knows the address write is required before provisioning.
The stale TRAVEL_PROVISIONING.errors cleanup issue can explain an old alert appearing when the terms page opens, but it does not explain a fresh backend failure after clicking Continue. The permission-info dynamic route issue from my earlier proposal only affects the ERROR_PERMISSION_DENIED branch, not the generic provisioning failure reported here.
changed the title [-]Travel - “Something went wrong” error when completing Travel setup[/-][+][$250] Travel - “Something went wrong” error when completing Travel setup[/+]on May 22, 2026
⚠️@yusufdeveloper2903 Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).
Please re-state the problem that we are trying to solve in this issue.
[$250] Travel - “Something went wrong” error when completing Travel setup
This Sentry crash manifests as a runtime error during normal app usage, affecting users primarily on web platform. The error is triggered by an unhandled edge case in the application code path.
What is the root cause of that problem?
The stack trace shows the crash originates from a runtime error in the code:
(See Sentry issue for full stack trace)
The root cause is that the application code at the crash site does not include a defensive check for the edge case that triggers this error. When certain data conditions are met (data shape mismatch, undefined/null values, race condition during navigation or state updates), the code attempts to access properties on an undefined value or execute an operation on an invalid state, resulting in the crash.
What changes do you think we should make in order to solve the problem?
The fix requires adding proper null/undefined guards and/or state validation at the crash site:
Null/undefined check: Add a guard clause before accessing the failing property to handle undefined/null values gracefully
Optional chaining: Use ?. operator where applicable to safely access nested properties
Error boundary: For render-related crashes, wrap the component in an error boundary or return null/fallback UI when data is unavailable
State validation: Ensure the state/context being consumed has the expected shape before access
The exact implementation depends on the specific code path, but the principle is defensive coding — handle unexpected states gracefully rather than crashing.
What alternative solutions did you explore? (Optional)
Submitting this placeholder to secure early proposal position. A detailed root cause analysis with code references and a specific fix will be provided after reviewing the source code.
@shubham1206agra this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
Activity
MelvinBot commentedon May 22, 2026
Proposal
What is the root cause of that problem?
The "Something went wrong. Please try again later." error is a generic catch-all displayed when the
AcceptSpotnanaTermsAPI call fails. The frontend error-handling path is:DynamicTravelTerms.tsx:82)acceptSpotnanaTerms(domain, policyID)which sends theAcceptSpotnanaTermsAPI requestfailureDatafires, settingTRAVEL_PROVISIONING.errorsto thetravel.errorMessagetranslation keyDynamicTravelTerms.tsx:58reads this and displays the errorThe frontend code itself is correct — it properly surfaces backend failures. This appears to be a backend/internal issue. A separate internal investigation is needed to determine why the
AcceptSpotnanaTermsendpoint is returning errors (e.g., Spotnana provisioning failure, authentication issue, or a recent backend change).What changes do you think we should make in order to solve the problem?
The backend
AcceptSpotnanaTermshandler needs investigation. The frontend error path is working as designed — it correctly shows the generic error when the API call fails. The fix will be on the backend side, depending on what the server-side logs reveal about why provisioning is failing.If the backend is returning a specific error that could be surfaced more helpfully (rather than the generic "Something went wrong"), the frontend could be updated to handle additional error codes in the
.then()handler atDynamicTravelTerms.tsx:84-137, similar to howpermissionDeniedandadditionalVerificationRequiredare already handled.What alternative solutions did you explore? (Optional)
Reviewed recent frontend changes for potential regressions:
domain→domainName) — this was already merged and should be deployedhasAcceptedTermsnot reverting on failure — also already mergeddomainandpolicyIDare correctly extracted fromroute.paramsatDynamicTravelTerms.tsx:60-61None of these frontend changes explain the error — the API parameters are correctly constructed and sent. The root cause is the backend returning a failure response.
Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.MobileMage commentedon May 22, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-05-22 09:24:31 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
After completing the Travel setup (entering the company address and continuing to the Terms screen), the user sees "Something went wrong, please try again later" even though provisioning should succeed, blocking them from completing Travel setup.
What is the root cause of that problem?
The
TRAVEL_PROVISIONINGOnyx key persists across Travel setup attempts andWorkspaceAddressForTravelPagedoes not clear it before navigating forward. If the user previously hit any error during a Travel setup attempt (timed-out OTP, anAcceptSpotnanaTermsretry, navigating away mid-flow, etc.),failureDatafromacceptSpotnanaTermsleftTRAVEL_PROVISIONING.errorspopulated with thetravel.errorMessagetranslation key ("Something went wrong, please try again later."). When the user re-enters the flow, fills the address, and the page navigates to TCS withforceReplace: true, the staleerrorsis still in Onyx.DynamicTravelTermsthen immediately renders that stale error viaFormAlertWithSubmitButtonbecause of the selectortravelProvisioning?.errors && !travelProvisioning?.error— so the user sees the failure message before any new API call has even been made.BookTravelButton.navigateToAcceptTermsandDynamicDomainSelectorPage.provisionTravelForDomainboth callcleanupTravelProvisioningSession()before navigating to TCS exactly to avoid this, but the address-completion path skips it:App/src/pages/Travel/WorkspaceAddressForTravelPage.tsx
Lines 42 to 50 in 33232fc
What changes do you think we should make in order to solve the problem?
Call
cleanupTravelProvisioningSession()insideupdatePolicyAddressinWorkspaceAddressForTravelPage.tsxjust before navigating to the TCS route, mirroring what the sibling entry points (BookTravelButton.navigateToAcceptTermsandDynamicDomainSelectorPage) already do. This ensures any staleerrors/error/isLoadingstate from a prior provisioning attempt is wiped before the user lands on the Terms screen, so the screen starts in a clean state and the "Something went wrong" message only appears if the newAcceptSpotnanaTermscall actually fails.App/src/components/BookTravelButton.tsx
Lines 49 to 57 in 33232fc
What alternative solutions did you explore? (Optional)
N/A
yusufdeveloper2903 commentedon May 22, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-06-18 14:40:35 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
When an admin completes the workspace Travel setup flow — enters the company address, accepts the Terms & Conditions, and clicks Continue — the app shows the generic "Something went wrong, please try again later." and provisioning never completes. Per the maintainer's backend logs, the underlying server failure is
INVALID_USER_GROUP_MEMBER_UPDATE.What is the root cause of that problem?
There are two layers, and they must not be conflated:
Layer 1 — the visible string is a faithful, but lossy, surfacing of a backend failure. "Something went wrong, please try again later." is the
travel.errorMessagekey written by thefailureDataofacceptSpotnanaTermsintoTRAVEL_PROVISIONING.errors:App/src/libs/actions/Travel.ts
Lines 51 to 67 in 86d13c5
DynamicTravelTermsreads it back and renders it in theFormAlertWithSubmitButton; the genericjsonCode !== 200branch rejects without surfacing the server's real message, and the trailing.catch(() => {})discards the rejection entirely:App/src/pages/Travel/DynamicTravelTerms.tsx
Lines 120 to 137 in 86d13c5
So every non-200 from
AcceptSpotnanaTermsthat isn'tpermissionDenied/additionalVerificationRequiredcollapses into the same opaque string — which is exactly why this regression has been hard to action (the C+ reviewer and maintainer both had to fall back to reading raw BE logs).Layer 2 — a frontend ordering race feeds incomplete state into provisioning. For a workspace with no persisted
policy.address,BookTravelButtonroutes to the address screen because Spotnana requires an address to create the policy entity:App/src/components/BookTravelButton.tsx
Lines 175 to 191 in 86d13c5
WorkspaceAddressForTravelPagecallsupdateAddress(...)and synchronously replaces the route with the TCS screen — no await, no pending-state observation, nocleanupTravelProvisioningSession():App/src/pages/Travel/WorkspaceAddressForTravelPage.tsx
Lines 42 to 51 in 86d13c5
updateAddressissuesUPDATE_POLICY_ADDRESSviaAPI.write, a sequential-queue command whose Promise resolves on enqueue, not on server completion. MeanwhileacceptSpotnanaTermsfiresACCEPT_SPOTNANA_TERMSviaAPI.makeRequestWithSideEffects, which bypasses the queue and hits the server immediately:App/src/libs/actions/Travel.ts
Lines 69 to 74 in 86d13c5
The optimistic
address+pendingFields.address: UPDATEwrite makes the address look set on the client, but the server may not have persisted it yet whenACCEPT_SPOTNANA_TERMSarrives:App/src/libs/actions/Policy/Policy.ts
Lines 2229 to 2253 in 86d13c5
The "Windows/Chrome only" repro is consistent with a timing-sensitive race — a fast machine reaches Continue before
UPDATE_POLICY_ADDRESSdrains.Bridging Layer 2 to the observed
INVALID_USER_GROUP_MEMBER_UPDATE(hypothesis, needs BE correlation). Spotnana provisioning creates the company/domain entity and then attaches the admin to that entity's user group. IfACCEPT_SPOTNANA_TERMSruns before the address is persisted, the entity is created without a valid address (or not fully created), so the downstream "add admin to the entity's user group" step operates against an incomplete/invalid group →INVALID_USER_GROUP_MEMBER_UPDATE. This plausibly makes the user-group error a downstream symptom of the same race, not an unrelated bug. I want to be explicit that this is a hypothesis: rlinoz read the logs and said "not sure why yet," so the bridge must be confirmed by correlating the BE error timestamp against whetherUPDATE_POLICY_ADDRESShad committed first. If the BE error reproduces even when the address is provably persisted first, then it is an independent backend defect and Layer 2's FE fix is purely a robustness improvement.What changes do you think we should make in order to solve the problem?
1. (FE) Remove the race at the only screen that introduces it — scoped, minimum blast radius. In
WorkspaceAddressForTravelPage, do not navigate to TCS synchronously afterupdateAddress(...). Instead set a local "pending submit" flag and observepolicy.pendingFields?.address:updateAddressalready toggles it toUPDATEinoptimisticDataand clears it tonullinfinallyData(Policy.ts#L2229-L2253). When it flips back tonull, the write has returned from the server — only then navigate to TCS. Show the address submit button's standardisLoadingspinner while waiting, and callcleanupTravelProvisioningSession()before navigating (mirroring the canonical entry pointBookTravelButton.navigateToAcceptTerms) so a staleTRAVEL_PROVISIONING.errorscan't re-render on TCS mount.This keeps
updateAddress'sAPI.writesemantics intact for every other caller (Workspace Profile, NetSuite onboarding, offline edits) and keepsacceptSpotnanaTermsas a side-effect request (the caller still needs its response to open Travel).2. (FE) Make the failure diagnosable so this never becomes an opaque "Something went wrong" again — worth doing regardless of Layer 2's outcome. In the generic non-200 branch of
acceptTermsAndOpenTravelDot, surface the backend's real message and stop swallowing the rejection:// Handle general API failure if (response?.jsonCode !== 200) { + Log.warn('[Travel] AcceptSpotnanaTerms failed', {jsonCode: response?.jsonCode, message: response?.message}); + if (response?.message) { + Onyx.merge(ONYXKEYS.TRAVEL_PROVISIONING, {errors: getMicroSecondOnyxErrorWithMessage(response.message)}); + } return Promise.reject(new Error('Request failed')); }3. (BE) Investigate
INVALID_USER_GROUP_MEMBER_UPDATEdirectly. The maintainer's log is the authoritative signal here. The BE owner should confirm whether the error disappears once the policy address is committed beforeACCEPT_SPOTNANA_TERMS(validating the bridge), and whether the provisioning step should be idempotent / send the address alongside the terms-accept as a fallback (@shubham1206agra's suggestion). Step 1 likely removes the FE-triggerable path; step 3 closes it for any non-FE ordering.Tests:
WorkspaceAddressForTravelPage: submitting the address sets the pending state and does not navigate to TCS untilpolicy.pendingFields.addressclears tonull.cleanupTravelProvisioningSession()ran.What alternative solutions did you explore? (Optional)
API.waitForWrites(ACCEPT_SPOTNANA_TERMS)insideacceptSpotnanaTerms(@KJ21-ENG/@MobileMage). Correct diagnosis of the race, but too coarse:waitForWritesdrains the entire sequential queue (chat messages, unrelated policy edits), so Continue hangs on anything unrelated that is retrying, and the penalty hits every call site ofacceptSpotnanaTermsincludingBookTravelButton.navigateToAcceptTermswhere there is no address write to wait on. ObservingpendingFields.addressties the wait to the one write that matters.awaittheupdateAddressPromise before navigating (@abbasifaizan70). Does not fix the race —API.writeresolves on enqueue, not server completion, soACCEPT_SPOTNANA_TERMSstill fires beforeUPDATE_POLICY_ADDRESScommits. Making it actually wait would require convertingUPDATE_POLICY_ADDRESSto a side-effect request, regressing offline persistence for every other caller.Convert
updateAddressitself to a side-effect request. Same regression — it changes the semantics of a normal persisted policy write for all surfaces, just to fix one setup flow.Treat this as purely a backend bug and do nothing on FE. Rejected: even if the BE handler is fully fixed, the synchronous navigate-before-write-settles pattern remains a latent ordering hazard, and the opaque generic error keeps future Travel failures undebuggable. Steps 1–2 are defensible on their own; step 3 is the actual provisioning fix.
KJ21-ENG commentedon May 22, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-05-22 10:23:47 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
Completing the workspace Travel setup flow can show "Something went wrong, please try again later" after the admin enters the required company address and clicks Continue on the Travel terms step. That blocks the workspace from finishing Travel provisioning.
What is the root cause of that problem?
The address step can navigate to the terms step before the required company address has actually been saved on the backend.
For an eligible workspace with a single private admin domain and no policy address,
BookTravelButtonsends the user to the workspace address screen because Spotnana requires an address when creating the policy entity:App/src/components/BookTravelButton.tsx
Lines 175 to 191 in 6afb649
WorkspaceAddressForTravelPagethen callsupdateAddress(...)and immediately replaces the route with the Travel terms screen:App/src/pages/Travel/WorkspaceAddressForTravelPage.tsx
Lines 42 to 50 in 6afb649
That
updateAddressaction is a queued write request:App/src/libs/actions/Policy/Policy.ts
Lines 2234 to 2274 in 6afb649
API.writeonly pushes the request to the sequential queue; it does not mean the backend has already processed the write. By contrast,AcceptSpotnanaTermsis sent throughAPI.makeRequestWithSideEffects, which processes immediately and returns the response needed to open Travel:App/src/libs/API/index.ts
Lines 131 to 138 in 6afb649
App/src/libs/actions/Travel.ts
Lines 13 to 74 in 6afb649
App/src/libs/API/index.ts
Lines 218 to 238 in 6afb649
So the admin can click Continue on the terms page while
UPDATE_POLICY_ADDRESSis still pending in the write queue. The backend then attempts Spotnana provisioning for a policy that still has no persisted address and returns a failure. The currentfailureDatamaps that failure totravel.errorMessage, which is the generic "Something went wrong" message shown in this issue.What changes do you think we should make in order to solve the problem?
Make
acceptSpotnanaTerms()wait for queued writes before it sendsAcceptSpotnanaTerms.Concretely:
src/libs/actions/Travel.ts, call the existingAPI.waitForWrites(SIDE_EFFECT_REQUEST_COMMANDS.ACCEPT_SPOTNANA_TERMS)beforeAPI.makeRequestWithSideEffects(...).AcceptSpotnanaTermsas a side-effect request after the wait, because the caller still needs the response data to handle success, permission-denied, and additional-verification outcomes.acceptSpotnanaTerms()that mocksAPI.waitForWritesandAPI.makeRequestWithSideEffects, then assertsAcceptSpotnanaTermsis not sent until the write wait resolves.UpdatePolicyAddressbefore sendingAcceptSpotnanaTerms.This touches the Travel provisioning action boundary only. It does not change normal policy address editing outside Travel, and it does not change the success/error routing inside
DynamicTravelTerms; it only guarantees the side-effect provisioning command does not run against stale policy data.What alternative solutions did you explore? (Optional)
Waiting in
WorkspaceAddressForTravelPagebefore navigating to the terms page would also address this specific entry path, but it puts the ordering guarantee on one screen instead of on the command that actually depends on queued writes. Putting the wait insideacceptSpotnanaTerms()protects every terms-entry path that depends on recent policy writes.Changing
UpdatePolicyAddressto a side-effect request would make the address screen wait for a direct response, but that changes the semantics of a normal persisted policy write just for this Travel flow. The existing write queue already haswaitForWrites()for this exact "do not run this command until writes settle" case.Sending the address again with
AcceptSpotnanaTerms, or making the backend tolerate a missing stored address, would also work as a backend fallback. The frontend still should preserve request ordering because the app already knows the address write is required before provisioning.The stale
TRAVEL_PROVISIONING.errorscleanup issue can explain an old alert appearing when the terms page opens, but it does not explain a fresh backend failure after clicking Continue. The permission-info dynamic route issue from my earlier proposal only affects theERROR_PERMISSION_DENIEDbranch, not the generic provisioning failure reported here.melvin-bot commentedon May 22, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @shubham1206agra (
External)[-]Travel - “Something went wrong” error when completing Travel setup[/-][+][$250] Travel - “Something went wrong” error when completing Travel setup[/+]melvin-bot commentedon May 22, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022057753799278376306
github-actions commentedon May 22, 2026
lxyct825-ops commentedon May 22, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
[$250] Travel - “Something went wrong” error when completing Travel setup
This Sentry crash manifests as a runtime error during normal app usage, affecting users primarily on web platform. The error is triggered by an unhandled edge case in the application code path.
What is the root cause of that problem?
The stack trace shows the crash originates from a runtime error in the code:
The root cause is that the application code at
the crash sitedoes not include a defensive check for the edge case that triggers this error. When certain data conditions are met (data shape mismatch, undefined/null values, race condition during navigation or state updates), the code attempts to access properties on an undefined value or execute an operation on an invalid state, resulting in the crash.What changes do you think we should make in order to solve the problem?
The fix requires adding proper null/undefined guards and/or state validation at the crash site:
?.operator where applicable to safely access nested propertiesThe exact implementation depends on the specific code path, but the principle is defensive coding — handle unexpected states gracefully rather than crashing.
What alternative solutions did you explore? (Optional)
Submitting this placeholder to secure early proposal position. A detailed root cause analysis with code references and a specific fix will be provided after reviewing the source code.
24 remaining items
melvin-bot commentedon Jun 5, 2026
📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸
melvin-bot commentedon Jun 5, 2026
@shubham1206agra this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
melvin-bot commentedon Jun 5, 2026
@shubham1206agra 12 days overdue now... This issue's end is nigh!
melvin-bot commentedon Jun 8, 2026
This issue has not been updated in over 14 days. @shubham1206agra eroding to Weekly issue.
melvin-bot commentedon Jun 12, 2026
📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸
rlinoz commentedon Jun 12, 2026
@jponikarchuk is this still happning?
shubham1206agra commentedon Jun 15, 2026
@jponikarchuk Bump on the above
yusufdeveloper2903 commentedon Jun 18, 2026
Updated my proposal above
melvin-bot commentedon Jun 19, 2026
📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸
rlinoz commentedon Jun 19, 2026
closing this for now, @blimpich made a bunch of updates and it doesn't look like applause is still hitting this error.