Skip to content

fix: preserve originalError on EABORT TransactionError#1850

Merged
dhensby merged 1 commit into
tediousjs:masterfrom
dhensby:fix/tx-original-error
May 15, 2026
Merged

fix: preserve originalError on EABORT TransactionError#1850
dhensby merged 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
@dhensby dhensby merged commit 61608d0 into tediousjs:master May 15, 2026
47 checks passed
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 12.5.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

@dhensby dhensby deleted the fix/tx-original-error branch May 18, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

originalError undefined in some transaction errors

2 participants