Repository navigation
Description
Problem
When a NetSuite sync fails for any reason during connection initialization, the UI always shows:
Can't connect to NetSuite due to an authentication error
...even when the credentials/tokens are completely valid and the real failure has nothing to do with authentication. This sends users down the wrong remediation path (re-entering already-valid credentials) instead of the one that would actually fix the problem.
Root cause
In Integration-Server/src/expensify/netsuite/NetSuiteSync.java, the syncPolicy/auto-sync path wraps initNetSuiteConnection() in a catch block that unconditionally reports the failure as an authentication error, regardless of what was actually thrown:
// NetSuiteSync.java:1574-1586
try {
initNetSuiteConnection(policyID, importType);
} catch (ExpensifyException | JSONException e) {
String userErrorMessage = "Unexpected error.";
if (e instanceof ExpensifyException) {
userErrorMessage = e.getMessage();
}
...
syncErrorMessages.add(userErrorMessage);
Log.exception(getLogEmail(), Level.AUTO, "Exception encountered while initializing NetSuite data.", e);
updateConnectionInfo(policyID, false, true); // <-- isAuthenticationError hardcoded to true
...
}initNetSuiteConnection() constructs a NetSuiteAPI, which can throw for reasons that have nothing to do with credentials — for example, a missing subsidiary selection:
// NetSuiteAPI.java:253-256
if (populateCache && (subsidiaryName == null || subsidiaryName.isEmpty())) {
Log.hmmm(logEmail, "Subsidiary name is null/empty in NetSuiteAPI()!");
throw new ExpensifyException("An error occured while getting data from NetSuite. Make sure you have selected an existing subsidiary for your connection, and please try again.");
}The correct, specific error message is captured and stored in syncErrorMessages/errorMessage, but the isAuthenticationError boolean passed to updateConnectionInfo is hardcoded true regardless of the exception's actual cause.
This is reachable on a brand new connection: NetSuiteInitConnection.java always creates a fresh connection with config.put("subsidiary", "") (NetSuiteInitConnection.java:95) and immediately calls policyAPI.updateNetSuiteAutoSync(true) (NetSuiteInitConnection.java:79), which queues a full sync. If that sync runs before the user has picked a subsidiary in the accounting config UI, it hits the exact exception above and gets mislabeled as an auth failure.
Compounding frontend issue
Because isAuthenticationError is set, App/src/components/ConnectToNetSuiteFlow/index.tsx:40,64 redirects the user straight back to the credential re-entry page instead of the normal accounting config screen:
const shouldGoToCredentialsPage = isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.NETSUITE);
...
useEffect(() => {
if (shouldGoToCredentialsPage || !hasReusablePoliciesConnectedToNetSuite) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID, getInitialSubPageForNetsuiteTokenInput(policy)));
return;
}
...
}, []);So a user hitting this bug can't even reach the screen where they'd fix the actual problem (selecting a subsidiary) — re-entering the same valid credentials just loops back into the same failure once the queued sync runs again.
Suggested fix
- Backend (Integration-Server): only pass
isAuthenticationError = truewhen the underlying exception actually came from the NetSuite login/token-validation step, not from any exception raised anywhere ininitNetSuiteConnection(). Consider a dedicated exception type/flag (e.g.NetSuiteAuthenticationException) so callers can distinguish "bad credentials" from other setup errors like "no subsidiary selected." - Frontend (App):
ConnectToNetSuiteFlowshould not assume everyisAuthenticationErrorimplies credentials need to be re-entered — or, at minimum, the backend should surface a distinct, actionable error/route for "connection succeeded, but setup is incomplete" (e.g. missing subsidiary) so the user lands on the config screen instead of the credentials screen.
How this was found
Reproduced while debugging a NetSuite connection in a local Expensidev dev environment. Syslog trail from Integration-Server (java: / Integration: tagged lines) showed token login succeeding (NetSuiteTokenAuthSession.java:82 login ... NetSuite TBA login, NetSuiteInitConnection.java:70 Successfully validated NetSuite credentials), followed ~10 minutes later by an auto-sync job failing with errorMessage: "...Make sure you have selected an existing subsidiary..." but isAuthenticationError: true in the resulting SavePolicy Onyx update.
Not a duplicate of #87227 (closed) — that issue's proposed root cause was a shouldUseUpdateNetSuiteTokens frontend routing regression from PR #83434 sending UpdateNetSuiteTokens instead of ConnectPolicyToNetSuite on retry. This issue is a distinct, still-present backend misclassification bug unrelated to that code path.
Activity
roryabraham commentedon Aug 12, 2026
no update
melvin-bot commentedon Sep 4, 2026
This issue has not been updated in over 15 days. @roryabraham eroding to Monthly issue.
P.S. Is everyone reading this sure this is really a near-term priority? Be brave: if you disagree, go ahead and close it out. If someone disagrees, they'll reopen it, and if they don't: one less thing to do!
roryabraham commentedon Sep 4, 2026
put out a call for volunteers
melvin-bot commentedon Sep 28, 2026
This issue has not been updated in over 15 days. @akinwale, @roryabraham eroding to Monthly issue.
P.S. Is everyone reading this sure this is really a near-term priority? Be brave: if you disagree, go ahead and close it out. If someone disagrees, they'll reopen it, and if they don't: one less thing to do!
roryabraham commentedon Oct 8, 2026
@MelvinBot can you please confirm if this was completed by https://github.com/Expensify/Integration-Server/pull/9324, which was deployed on September 7th?
MelvinBot commentedon Oct 8, 2026
I can't confirm it. I don't have access to that repository, and log search wasn't available in this run. So I couldn't read the PR or check whether the mislabeling stopped after September 7.
One part I can confirm: the App side has not changed.
ConnectToNetSuiteFlow/index.tsx:40still sends users to the credentials page wheneverisAuthenticationErroris set. That is fine if the backend now sets the flag only for real auth failures.To close this out, an internal engineer should check that the PR stops flagging non-auth failures (for example, a missing subsidiary) as authentication errors.
view run
roryabraham commentedon Oct 8, 2026
Confirmed completed by Integration-Server PR #9324. The PR adds
NetSuiteAuthenticationException, marksisAuthenticationErroronly for actual NetSuite/OAuth authentication failures, and preserves non-auth failures (such as a missing subsidiary) as non-auth errors. It was merged and deployed to Integration-Server production on September 7, 2026. Closing as completed.