0X8004D10B

Fix XACT_E_DUPLICATE_TRANSID (0x8004D10B) in SQL Server and MSDTC

XACT_E_DUPLICATE_TRANSID hits when a transaction ID is reused — usually a retry bug, a stale MSDTC transaction, or a double-enlist. Here's how to fix it.

Quick answer: Call Dispose() on the old TransactionScope, never reuse a transaction ID, flush or enumerate stale MSDTC transactions with dtcping, and if a specific GUID is stuck, recycle the MSDTC service to clear it.

What's actually happening here is that something handed MSDTC (or the SQL Server resource manager) a transaction object whose identifier is already registered on that coordinator. The COM+ epoch matters: every transaction gets a GUID plus a sequence number. If you submit the same combination twice — from a retry loop, a cloned scope, or a COM+ component that never released its context — the resource manager rejects the second enlist with XACT_E_DUPLICATE_TRANSID. The error surfaces as 0x8004D10B in HRESULT form, which sometimes gets rendered as a decimal like -2147163893 depending on the language.

The scenario I keep seeing in the wild: an ASP.NET app wraps a TransactionScope in a retry helper. The helper catches a transient SQL deadlock, rebuilds the work, but keys the retry off the original transaction ID it stashed in a cache. Second attempt enlists a fresh scope that MSDTC sees as the same transaction. Boom. The connection string check passes, the database is healthy, but the enlist dies before a single row is touched.

Fix steps

  1. Kill the retry-reuse pattern. If your code creates a new TransactionScope inside a retry loop, make sure the scope is fully disposed before the next iteration starts. Wrap it in a using block and never stash the transaction object outside it.
    for (int attempt = 0; attempt < 3; attempt++)
    {
        using (var scope = new TransactionScope(TransactionScopeOption.Required))
        {
            DoWork();          // enlist happens here
            scope.Complete();
        }                      // Dispose fires, GUID releases
    }
    If you were holding the TransactionScope outside the loop because "it's faster to reuse," stop. You can't reuse it. That's the whole error.
  2. Look for double-enlist in ADO.NET. Opening two SqlConnection objects inside the same scope against the same SQL Server instance, then issuing commands on both, can confuse the enlistment if the connection pool returns a connection that still has an ambient transaction reference from a prior request. Add Enlist=false to the connection string temporarily and enlist manually — if the error clears, you've found the culprit. Then fix the pool hygiene instead of leaving Enlist=false in production.
  3. Check MSDTC itself for a stuck transaction. On the SQL Server host, open Component Services (dcomcnfg), walk to Component Services → Computers → My Computer → Distributed Transaction Coordinator → Transaction Statistics. Set the statistic filter to show all. If you see a transaction in the In Doubt or Prepared state that matches the GUID from your error, that's your duplicate. Right-click → Resolve → Commit or Abort depending on what the log says.
  4. Recycle MSDTC if a specific GUID stays wedged. Sometimes the transaction is orphaned and neither Commit nor Abort clears it. From an elevated prompt:
    net stop msdtc
    net start msdtc
    That wipes the coordinator's in-memory transaction table. Any application with an in-flight distributed transaction during the restart will get a different error (usually XACT_E_CONNECTION_DOWN), so do this during a maintenance window. On a cluster, fail the MSDTC resource over instead — net stop on a clustered instance fights the cluster service.
  5. Verify promotion is happening where you think. A single SqlConnection against one SQL Server stays a local transaction and never touches MSDTC. The moment you open a second connection, or hit a second resource manager (a second SQL Server, an MQ queue, an Oracle link), the transaction promotes. Promotion is not transactional in the sense people assume — if promotion fails halfway, the local transaction can be left registered while the distributed one never gets a clean ID. Check Transaction Statistics for a stale local registration matching the SQL Server connection's SPID.

If the main fix doesn't work

  • Bypass MSDTC entirely. If you can't reproduce with a single connection, you don't actually need a distributed transaction — you need a local one. Collapse the two connections into one, or use TransactionScopeOption.Suppress around the read-only side.
  • Restart the application pool, not just the app. ASP.NET keeps ambient transaction state on the thread. A stale Transaction.Current from a previous request thread that got recycled into the pool with a live GUID will fire this error on the next request. iisreset is heavy; recycling the app pool is usually enough.
  • Check COM+ components. If the error comes from a legacy COM+ serviced component, its SetComplete/SetAbort calls have to match. Calling SetComplete twice on the same context, or calling it after the context was already returned to the pool, throws this exact HRESULT. Trace the component with the COM+ Trace Viewer.
  • Look at the SQL Server error log around the timestamp. Enlistment failures sometimes leave breadcrumbs like The transaction manager has disabled its support for remote/network transactions. That's a different problem (DTC network access), but it can surface as XACT_E_DUPLICATE_TRANSID if the enlist half-succeeds before the network check fails.

Prevention

Treat transaction IDs as single-use. The reason step 1 works is that a TransactionScope's identifier is only valid inside its own lifetime — dispose it and the GUID is retired. If you're caching transaction objects, serializing them, or passing them across thread boundaries (looking at you, Task.Run inside a scope), you're going to keep hitting this. Also keep retry logic outside the scope boundary, not inside it. And on any server that runs distributed work, monitor MSDTC's transaction statistics weekly — a growing list of prepared-but-not-committed transactions is the warning sign that precedes this error by hours or days.

One more thing: don't fix this by disabling transaction promotion with TransactionScopeOption.Suppress unless you've confirmed the second resource doesn't need to be part of the same unit of work. That "fix" trades a loud error for silent data inconsistency, which is worse.

Related Errors in Database Errors
40P01 PostgreSQL Deadlock Detected: Quick Fix That Works 18456 SQL Server Error 18456 Login Failed for User ORA-00600 ORA-00600 [4194] Fix: Undo Recovery Without Losing Data 9002 SQL Server disk full: force shrink log file fast

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.