Skip to content

formatError never sees real Xero errors: xero-node v13 rejects with a JSON string, not an object #213

Description

@brendanerofeev

Summary

Every failed Xero API call is reported to the LLM as:

An unexpected error occurred while communicating with Xero.

Xero's actual explanation — validation messages, PostDataInvalidException details, everything — is discarded before it reaches the caller. This affects all tools, and it makes any write failure effectively undiagnosable from the client side.

Root cause

xero-node v13 issues requests through axios, which throws on any non-2xx response. The generated API methods catch that and re-reject with a string:

// node_modules/xero-node/dist/gen/api/accountingApi.js
try {
    const response = await axios(localVarRequestOptions);
    ...
    else { reject({ response: response, body: body }); }
} catch (error) {
    const errorResponse = new ApiError(error);
    reject(JSON.stringify(errorResponse.generateError()));   // <-- a string
}

formatError has branches for AxiosError, an object-shaped SDK error, and Error — but none for a string, so everything falls through to the catch-all at src/helpers/format-error.ts:78.

Two consequences worth calling out:

  1. The isXeroSdkError branch (added in fix: prevent token leak in formatError for xero-node SDK errors #173) is unreachable for accounting calls. Because axios throws first, the SDK never reaches its reject({response, body}) path.
  2. improvement: extract and surface Xero validation errors on 400 responses #112 aims squarely at this problem — surfacing validation errors on 400s — but attaches the fix to error instanceof AxiosError, which also never fires for SDK calls. The intent is right; the attachment point can't be reached.

Reproduction

Any write with a value Xero rejects. Example — create-manual-journal currently sends NO_TAX, which Xero refuses (separate bug, see #182):

{ "ErrorNumber": 14, "Type": "PostDataInvalidException",
  "Message": "JSON request body could not be read, Error converting value \"NO_TAX\" to type 'Xero.API.Library..LineAmountType'. Path 'ManualJournals[0].LineAmountTypes', line 1, position 399.." }

The tool reports only Error creating manual journal: An unexpected error occurred while communicating with Xero.

Why this is worth fixing carefully

The rejected string embeds response.request.headers.authorization — the caller's Bearer token. So the fix cannot simply return the string, or it reintroduces exactly the leak #173 closed. It needs to parse and then whitelist.

Impact

In one real session, eight consecutive write attempts (4 update-bank-transaction, 4 create-manual-journal) failed against live books. All eight returned the identical generic message. The model concluded the Xero API was down and gave up — when in fact two distinct, fixable bugs were being reported clearly by Xero and thrown away here.

PR follows.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions