Skip to content

NetSuite connection failures are always reported as "authentication error" even when the real cause is unrelated (e.g. missing subsidiary) #98014

Closed

Description

@roryabraham

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 = true when the underlying exception actually came from the NetSuite login/token-validation step, not from any exception raised anywhere in initNetSuiteConnection(). 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): ConnectToNetSuiteFlow should not assume every isAuthenticationError implies 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.

Issue OwnerCurrent Issue Owner: @akinwale

Activity

  1. self-assigned this
    on Aug 7, 2026
  2. added
    InternalRequires API changes or must be handled by Expensify staff
    on Aug 7, 2026
  3. roryabraham commented on Aug 12, 2026

    @roryabraham
    ContributorAuthor

    no update

  4. melvin-bot commented on Sep 4, 2026

    @melvin-bot

    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!

  5. roryabraham commented on Sep 4, 2026

    @roryabraham
    ContributorAuthor
  6. self-assigned this
    on Sep 4, 2026
  7. melvin-bot commented on Sep 28, 2026

    @melvin-bot

    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!

  8. roryabraham commented on Oct 8, 2026

    @roryabraham
    ContributorAuthor

    @MelvinBot can you please confirm if this was completed by https://github.com/Expensify/Integration-Server/pull/9324, which was deployed on September 7th?

  9. MelvinBot commented on Oct 8, 2026

    @MelvinBot
    Contributor

    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:40 still sends users to the credentials page whenever isAuthenticationError is 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

  10. roryabraham commented on Oct 8, 2026

    @roryabraham
    ContributorAuthor

    Confirmed completed by Integration-Server PR #9324. The PR adds NetSuiteAuthenticationException, marks isAuthenticationError only 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

InternalRequires API changes or must be handled by Expensify staffMonthlyKSv2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions