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
[$175] Workspace Settings- Can't connect to Zoho Books #105694
Version Number: v9.5.28-4 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:#105240 Email or phone of affected tester (no customers): N/A Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: Windows 11 / Chrome, iPhone 13 / iOS 27 Beta, Samsung Galaxy A52 / Android 14 App Component: Workspace Settings
Action Performed:
Preconditions: logged in to a new Expensifail account, and create a control workspace on that account
Steps:
Navigate to Workspace setting -> Connections
Search for Zoho Books and click on it
Proceed with Zoho Books connections until navigated back to ND
Expected Result:
User can sucessfully connect to Zoho Books after navigated back to ND
Root Cause: This appears to be a backend/internal issue. A separate internal investigation is needed.
The App side looks correct. It opens the Zoho Books OAuth link the same way it opens Xero's, then waits for the backend to push the new connection to the workspace. If the backend saves neither a connection nor an error, the Connections page has nothing new to show, so "nothing happens."
Recommended Fix: No App change is recommended. An internal engineer should check why the Zoho Books OAuth callback doesn't create the connection in this flow. This likely overlaps with [Release 1.1] Zoho Books connection error is not displayed when the connect flow fails, which is waiting on a backend callback change so failures get saved and shown.
The beta isn't the cause. The tester could see and open Zoho Books.
Why the screen doesn't change
The App doesn't refetch when the user returns. WorkspaceConnectionsPage.tsx:124-128 fetches only on mount and on reconnect. Xero and QuickBooks Online work the same way, by design.
Alternative considered (not verified)
New workspaces have Connections turned off. The App turns it on with a queued write, then opens the setup flow one frame later (AccountingContext/index.tsx:150-171). If the callback refuses to connect while Connections is still off, the connection could fail. This matches the "new account + new workspace" precondition, but I couldn't confirm it.
Confidence: medium. The App code evidence is strong, but I couldn't check production logs in this run. Backend logs for the tester's ConnectPolicyToZohoBooks callback would confirm the failure.
Next Steps: Escalate to an internal engineer to check the Zoho Books OAuth callback, or reply with @MelvinBot <your feedback> to refine this analysis.
Please re-state the problem that we are trying to solve in this issue.
After completing the Zoho Books connection flow and returning to New Expensify, the workspace does not visibly show a successful connection. The report does not establish whether authorization failed, the connection was not persisted, or the interface failed to display the updated state.
What is the root cause of that problem?
Unverified. This case does not yet support a defensible implementation proposal.
No source snippets, callback trace, API responses, Onyx mutations, or failing automated tests were supplied. I could not retrieve PR #105240 or the relevant source through the available access methods, and no local Expensify checkout was found. Consequently, exact file paths, line numbers, and a causal TypeScript diff cannot be established without fabrication.
The reference to PR #105240 identifies the QA context; it does not establish that this PR introduced the defect.
The following explanations remain hypotheses:
Zoho authorization or the server-side token exchange fails.
The callback loses or incorrectly identifies the workspace.
The server establishes the connection but does not deliver the corresponding client update.
The client receives the update but renders stale connection state.
There is no evidence supporting a security vulnerability, credential disclosure, or CVSS assessment.
What changes do you think we should make in order to solve the problem?
Do not select a production change until the failing transition is demonstrated. A minimal diff must fix the verified cause; minimizing line count alone does not establish correctness.
Capture one reproduction against v9.5.28-4, with credentials and authorization codes redacted:
Record the connection initiation, callback destination, and workspace identifier.
Capture the connection-completion request and response, if present.
Compare persisted connection state with the client’s incoming Onyx updates.
Identify the first transition that differs from a successful connection.
Then patch the responsible layer and provide a commit-pinned source reference and failing-before/passing-after evidence.
A forced refresh, optimistic “connected” flag, or navigation change is currently unsupported: each could conceal an unsuccessful connection while making the interface appear fixed.
What alternative solutions did you explore? (Optional)
No MelvinBot proposal was supplied, so there is no concrete implementation to critique.
Reject these unsupported approaches:
Set success on return: returning from authorization does not prove that the connection was persisted.
Add a timeout or unconditional reload: neither identifies nor reliably repairs the failing transition.
Refactor shared OAuth handling: broadens regression exposure before establishing whether the defect is shared.
Existing proposals and duplicate issues were not verified; originality cannot be claimed.
What automated tests can you add to prevent this from re-occurring?
Once the responsible function or component is identified, add a regression test to its existing Jest suite:
Successful completion: seed a disconnected workspace, deliver the verified successful completion payload through the actual handler, and assert that the correct workspace displays the persisted connection without a manual reload.
Canceled or failed authorization: assert that returning to the app does not mark the workspace connected.
Workspace isolation: complete authorization for workspace A while workspace B exists; assert that B remains unchanged.
The primary test must fail on the affected revision and pass with the patch. Mocking the final connected state directly would bypass the suspected defect.
No tests were executed. Exact Jest code and a TypeScript diff remain blocked by unavailable source and reproduction evidence.
@lakchote It looks like there haven't been any callbacks, so I suspect the account hasn't been added to the account test yet. I think we should share the test email with QA so they can verify this flow.
Also, I think we should display error messages when something goes wrong so users know what happened.
Please re-state the problem that we are trying to solve in this issue.
After finishing the Zoho Books OAuth flow and coming back to New Expensify, the workspace shows nothing: Zoho Books isn't connected, no sync starts, and no error appears.
What is the root cause of that problem?
There are two parts. The connection itself is created on the backend. The App side only adds to the problem by never showing what happened.
The connection is never created (backend or Zoho app configuration).getZohoBooksSetupLink (src/libs/actions/connections/ZohoBooks.ts:36-41) only sends policyID, the same as Xero. The Zoho Books connection only appears once the backend pushes policy.connections.zohoBooks. In @huult's video no callback reaches the backend at all, which points to the Zoho OAuth client not accepting this test account. An internal engineer needs to check that.
The App never re-reads the connection state when the user comes back.WorkspaceConnectionsPage.tsx:124-128 fetches only on mount and on reconnect:
Web lands on the wrong page under the Connections beta. The web deep-link screen ZohoBooksOAuthPage/index.tsx:20-23 always goes back to the old Accounting page:
The native screen (ZohoBooksOAuthPage/index.native.tsx:24) already uses getAccountingConnectionsRoute(isUnifiedConnectionsBetaEnabled, policyID). So a web user who started from Workspace > Connections comes back to a different page from the one they left.
What changes do you think we should make in order to solve the problem?
Part 1 needs an internal fix: allow-list the test account or domain on the Zoho OAuth client, or fix the callback. The App changes below make sure the user always sees the result, whether it's a success or an error.
Re-fetch when the user comes back to the app. In WorkspaceConnectionsPage.tsx, also fetch when the app becomes visible again, using the existing Visibility helper (src/libs/Visibility). It works for both a web tab switch and a native app foreground:
OpenPolicyAccountingPage is a read, so an extra call when the tab regains focus is cheap. It brings back connections.zohoBooks (or its error) even if the Pusher update was missed.
Re-fetch when the native OAuth screen closes.AccountingSetupWebViewPage is a screen inside the app, so the visibility event doesn't fire when it closes. Re-fetch when the Connections page gets focus again:
With the backend fix, the user returns to Connections and sees Zoho Books syncing. Without it, the page now shows the latest backend state, including the connection error from #104933, instead of looking frozen.
What alternative solutions did you explore? (Optional)
Optimistically mark Zoho Books as "connecting" when OAuth starts. This would hide a real failure behind a fake status, and the App can't tell when the user gives up in the other tab.
Poll OpenPolicyAccountingPage while the OAuth tab is open. This adds repeated requests for a case that a single fetch on return already covers.
Automated Tests
In tests/ui/WorkspaceConnectionsPageTest.tsx:
Given the Connections page is mounted. When the app goes to the background and becomes visible again. ThenOpenPolicyAccountingPage is called again for the policy.
Given the backend responds with connections.zohoBooks. When that refetch resolves. Then Zoho Books appears in the connected list.
In tests/ui/ZohoBooksOAuthPageTest.tsx (web):
Given the unified Connections beta is on. When the OAuth deep-link screen opens. ThenNavigation.goBack is called with WORKSPACE_CONNECTIONS. With the beta off, it's called with POLICY_ACCOUNTING.
@huult QA already has access, and the callbacks did reach us. QA's tries last night failed with "Zoho Books organizations with multiple locations are not supported yet." That was because our test org briefly had a second location from our own testing. It's back to one location now, so I asked QA to retest with the same account. If it passes, I'll close this as a dupe of #104933, which is the App not showing that error.
The 3 tries right before your comment failed with "A Zoho Books Professional plan or higher is required to connect." If those were yours, your Zoho org needs to be on Professional or higher.
Activity
applause-bot commentedon Nov 2, 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/e2ccaae0ce60c62b3eaa8938c8f6558e3f95d500a3bc1ab0335a679cea173896
MelvinBot commentedon Nov 2, 2026
Issue Analysis
Root Cause: This appears to be a backend/internal issue. A separate internal investigation is needed.
The App side looks correct. It opens the Zoho Books OAuth link the same way it opens Xero's, then waits for the backend to push the new connection to the workspace. If the backend saves neither a connection nor an error, the Connections page has nothing new to show, so "nothing happens."
Recommended Fix: No App change is recommended. An internal engineer should check why the Zoho Books OAuth callback doesn't create the connection in this flow. This likely overlaps with [Release 1.1] Zoho Books connection error is not displayed when the connect flow fails, which is waiting on a backend callback change so failures get saved and shown.
Investigation details
App flow (no defect found)
src/libs/actions/connections/ZohoBooks.ts:36-41builds theConnectPolicyToZohoBooksURL with onlypolicyID. This matchesXero.ts:21-25. No parameter is missing.useStartZohoBooksOAuth/index.ts:20). Native loads it in a WebView (ZohoBooksOAuthPage/index.native.tsx).Why the screen doesn't change
WorkspaceConnectionsPage.tsx:124-128fetches only on mount and on reconnect. Xero and QuickBooks Online work the same way, by design.policy.connections.zohoBooks(PolicyUtils/connections.ts:79-81).lastSync.isSuccessfulisfalseanderrorDateis set (connections/index.ts:314-321).Alternative considered (not verified)
New workspaces have Connections turned off. The App turns it on with a queued write, then opens the setup flow one frame later (
AccountingContext/index.tsx:150-171). If the callback refuses to connect while Connections is still off, the connection could fail. This matches the "new account + new workspace" precondition, but I couldn't confirm it.Not caused by the QA'd PR
The bug also reproduces in production, so Add Zoho Books export settings UI, manual export, and Mark as exported didn't introduce it. That PR changes export settings, not the connect flow.
Confidence: medium. The App code evidence is strong, but I couldn't check production logs in this run. Backend logs for the tester's
ConnectPolicyToZohoBookscallback would confirm the failure.Next Steps: Escalate to an internal engineer to check the Zoho Books OAuth callback, or reply with
@MelvinBot <your feedback>to refine this analysis.view run
melvin-bot commentedon Nov 2, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @huult (
External)[-]Workspace Settings- Can't connect to Zoho Books[/-][+][$175] Workspace Settings- Can't connect to Zoho Books[/+]melvin-bot commentedon Nov 2, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022117389258391185509
camesenin commentedon Nov 2, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
After completing the Zoho Books connection flow and returning to New Expensify, the workspace does not visibly show a successful connection. The report does not establish whether authorization failed, the connection was not persisted, or the interface failed to display the updated state.
What is the root cause of that problem?
Unverified. This case does not yet support a defensible implementation proposal.
No source snippets, callback trace, API responses, Onyx mutations, or failing automated tests were supplied. I could not retrieve PR #105240 or the relevant source through the available access methods, and no local Expensify checkout was found. Consequently, exact file paths, line numbers, and a causal TypeScript diff cannot be established without fabrication.
The reference to PR #105240 identifies the QA context; it does not establish that this PR introduced the defect.
The following explanations remain hypotheses:
There is no evidence supporting a security vulnerability, credential disclosure, or CVSS assessment.
What changes do you think we should make in order to solve the problem?
Do not select a production change until the failing transition is demonstrated. A minimal diff must fix the verified cause; minimizing line count alone does not establish correctness.
Capture one reproduction against
v9.5.28-4, with credentials and authorization codes redacted:Then patch the responsible layer and provide a commit-pinned source reference and failing-before/passing-after evidence.
A forced refresh, optimistic “connected” flag, or navigation change is currently unsupported: each could conceal an unsuccessful connection while making the interface appear fixed.
Expensify’s contributing guidelines require reproduction before proceeding with a proposal.
What alternative solutions did you explore? (Optional)
No MelvinBot proposal was supplied, so there is no concrete implementation to critique.
Reject these unsupported approaches:
Existing proposals and duplicate issues were not verified; originality cannot be claimed.
What automated tests can you add to prevent this from re-occurring?
Once the responsible function or component is identified, add a regression test to its existing Jest suite:
The primary test must fail on the affected revision and pass with the patch. Mocking the final connected state directly would bypass the suspected defect.
No tests were executed. Exact Jest code and a TypeScript diff remain blocked by unavailable source and reproduction evidence.
Contributor details
Your Expensify account email: camesenin@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~01b60d704ba377be83
melvin-bot commentedon Nov 2, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
huult commentedon Nov 3, 2026
Screen.Recording.2026-11-03.at.10.01.48.mov
@lakchote It looks like there haven't been any callbacks, so I suspect the account hasn't been added to the account test yet. I think we should share the test email with QA so they can verify this flow.
Also, I think we should display error messages when something goes wrong so users know what happened.
voxturrlabs commentedon Nov 3, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
After finishing the Zoho Books OAuth flow and coming back to New Expensify, the workspace shows nothing: Zoho Books isn't connected, no sync starts, and no error appears.
What is the root cause of that problem?
There are two parts. The connection itself is created on the backend. The App side only adds to the problem by never showing what happened.
The connection is never created (backend or Zoho app configuration).
getZohoBooksSetupLink(src/libs/actions/connections/ZohoBooks.ts:36-41) only sendspolicyID, the same as Xero. The Zoho Books connection only appears once the backend pushespolicy.connections.zohoBooks. In @huult's video no callback reaches the backend at all, which points to the Zoho OAuth client not accepting this test account. An internal engineer needs to check that.The App never re-reads the connection state when the user comes back.
WorkspaceConnectionsPage.tsx:124-128fetches only on mount and on reconnect:useStartZohoBooksOAuth/index.ts:20). The Connections tab stays mounted, so it never fetches again.Web lands on the wrong page under the Connections beta. The web deep-link screen
ZohoBooksOAuthPage/index.tsx:20-23always goes back to the old Accounting page:The native screen (
ZohoBooksOAuthPage/index.native.tsx:24) already usesgetAccountingConnectionsRoute(isUnifiedConnectionsBetaEnabled, policyID). So a web user who started from Workspace > Connections comes back to a different page from the one they left.What changes do you think we should make in order to solve the problem?
Part 1 needs an internal fix: allow-list the test account or domain on the Zoho OAuth client, or fix the callback. The App changes below make sure the user always sees the result, whether it's a success or an error.
Re-fetch when the user comes back to the app. In
WorkspaceConnectionsPage.tsx, also fetch when the app becomes visible again, using the existingVisibilityhelper (src/libs/Visibility). It works for both a web tab switch and a native app foreground:OpenPolicyAccountingPageis a read, so an extra call when the tab regains focus is cheap. It brings backconnections.zohoBooks(or its error) even if the Pusher update was missed.Re-fetch when the native OAuth screen closes.
AccountingSetupWebViewPageis a screen inside the app, so the visibility event doesn't fire when it closes. Re-fetch when the Connections page gets focus again:This could replace the mount effect, because focus also fires on mount.
Return to the page the user started from on web. In
ZohoBooksOAuthPage/index.tsx, use the same route helper as the native screen:With the backend fix, the user returns to Connections and sees Zoho Books syncing. Without it, the page now shows the latest backend state, including the connection error from #104933, instead of looking frozen.
What alternative solutions did you explore? (Optional)
OpenPolicyAccountingPagewhile the OAuth tab is open. This adds repeated requests for a case that a single fetch on return already covers.Automated Tests
In
tests/ui/WorkspaceConnectionsPageTest.tsx:OpenPolicyAccountingPageis called again for the policy.connections.zohoBooks. When that refetch resolves. Then Zoho Books appears in the connected list.In
tests/ui/ZohoBooksOAuthPageTest.tsx(web):Navigation.goBackis called withWORKSPACE_CONNECTIONS. With the beta off, it's called withPOLICY_ACCOUNTING.Contributor details
Expensify account email: systemadmin@voxturrlabs.com
Upwork Profile Link: https://www.upwork.com/freelancers/~010efadac9ff3c3d2a/
lakchote commentedon Nov 3, 2026
@huult QA already has access, and the callbacks did reach us. QA's tries last night failed with "Zoho Books organizations with multiple locations are not supported yet." That was because our test org briefly had a second location from our own testing. It's back to one location now, so I asked QA to retest with the same account. If it passes, I'll close this as a dupe of #104933, which is the App not showing that error.
The 3 tries right before your comment failed with "A Zoho Books Professional plan or higher is required to connect." If those were yours, your Zoho org needs to be on Professional or higher.
IuliiaHerets commentedon Nov 3, 2026
Issue is not repro anymore, checked on Web, Android and iOS, v9.5.28-5
ScreenRecording_11-03-2026.16-18-35_1.mp4
Screen_Recording_20261103_162856_Expensify.mp4
bandicam.2026-11-03.16-24-03-297.mp4