Skip to content

fix: preserve originalError on EABORT TransactionError#1850

Open
dhensby wants to merge 1 commit into
tediousjs:masterfrom
dhensby:fix/tx-original-error
Open

fix: preserve originalError on EABORT TransactionError#1850
dhensby wants to merge 1 commit into
tediousjs:masterfrom
dhensby:fix/tx-original-error

Conversation

@dhensby
Copy link
Copy Markdown
Collaborator

@dhensby dhensby commented Apr 24, 2026

Problem

When a transaction is aborted by the server (e.g. due to XACT_ABORT or a deadlock), subsequent commit() or rollback() calls return a TransactionError with code EABORT. However, originalError was always undefined on these errors because the error was constructed with a string message rather than an Error object.

This made it difficult for consumers to understand why the transaction was aborted without correlating errors manually.

Closes #1716

Solution

  1. Capture the actual request error — When a request completes with an error and the transaction has been aborted, the error is stored as _abortReason on the transaction. This is done in the tedious request completion handlers (_query, _execute, _bulk).

  2. Generic fallback — The tedious _abort handler (fired by the rollbackTransaction event) sets a generic "Transaction was rolled back by the server" fallback reason for edge cases where the specific error cannot be captured (e.g. connection-level errors).

  3. Attach as originalError — A new _createAbortError() helper on the base Transaction class constructs the EABORT TransactionError and attaches _abortReason as originalError using Object.defineProperty (matching the pattern used elsewhere in MSSQLError).

Changes

  • lib/base/transaction.js — Added _abortReason tracking, _createAbortError() helper, reset on _begin()
  • lib/tedious/transaction.js — Generic fallback _abortReason in _abort handler
  • lib/tedious/request.js — Capture actual request error as abort reason in _query, _execute, _bulk completion paths
  • test/common/unit.js — 4 new unit tests for EABORT originalError behavior

Example

const tx = pool.transaction()
await tx.begin()
try {
  // This query triggers XACT_ABORT
  await tx.request().query("INSERT INTO ...")
} catch (requestErr) {
  // requestErr is the original SQL error
}
try {
  await tx.rollback()
} catch (abortErr) {
  // abortErr.code === "EABORT"
  // abortErr.originalError === requestErr  ← previously undefined
}

@dhensby dhensby added this to the v12.x milestone Apr 24, 2026
@dhensby dhensby force-pushed the fix/tx-original-error branch from 0af990b to bc36e09 Compare May 14, 2026 14:40
@dhensby dhensby requested a review from Copilot May 14, 2026 14:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR preserves the underlying abort cause on EABORT transaction errors so consumers can inspect originalError after server-aborted transactions.

Changes:

  • Tracks _abortReason on transactions and attaches it to generated EABORT errors.
  • Captures request errors in tedious request completion paths and provides a generic abort fallback.
  • Adds unit coverage for base transaction abort error behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
lib/base/transaction.js Adds abort reason tracking and EABORT error construction.
lib/tedious/transaction.js Sets a generic server-rollback abort reason fallback.
lib/tedious/request.js Attempts to capture request errors as transaction abort reasons.
test/common/unit.js Adds unit tests for EABORT originalError behavior.
Comments suppressed due to low confidence (2)

lib/tedious/request.js:509

  • In streaming mode error is never populated because the code only assigns it when !this.stream, so this branch cannot preserve the actual request error for an aborted streaming query. The request errors are still collected in errors for stream mode, so use the last collected error (when present) to set _abortReason before invoking the callback.
            if (error && this.parent._aborted) {
              this.parent._abortReason = error
            }

lib/tedious/request.js:876

  • In streaming mode error remains undefined because it is assigned only inside the !this.stream branch, so aborted streaming executions keep only the generic fallback instead of the actual request error. Since errors is populated for stream mode too, set _abortReason from the last collected error when the transaction has been aborted.
            if (error && this.parent._aborted) {
              this.parent._abortReason = error
            }

Comment thread lib/tedious/request.js Outdated
When a transaction is aborted (e.g. by XACT_ABORT or deadlock), subsequent
commit() or rollback() calls return a TransactionError with code EABORT.
Previously, originalError was always undefined on these errors because the
error was constructed with a string message rather than an Error object.

Now the actual request error that triggered the abort is captured and
attached as originalError on the EABORT TransactionError. A generic
fallback is used when the specific error cannot be captured (e.g.
connection-level errors).

Closes tediousjs#1716

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dhensby dhensby force-pushed the fix/tx-original-error branch from 48075c6 to b9e9826 Compare May 14, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

originalError undefined in some transaction errors

2 participants