0XC022000D

Fix STATUS_FWP_NO_TXN_IN_PROGRESS (0xC022000D) – Explicit Transaction Error

You called a transaction API outside an explicit transaction. The fix: wrap your code in a BEGIN/COMMIT or use the correct transaction scope.

What's Actually Happening Here

You're seeing STATUS_FWP_NO_TXN_IN_PROGRESS (0xC022000D) — the Windows Filtering Platform (WFP) is telling you that you tried to call a transaction-related API function without first opening a transaction. The WFP kernel-mode engine requires that certain calls (like adding or deleting filters) happen inside an explicit transaction. If you call FwpsTransactionCommit0 or FwpsTransactionAbort0 without a matching FwpsTransactionBegin0, this error pops up.

This error shows up in two real-world scenarios: (1) writing a WFP callout driver where you forgot to start the transaction, or (2) using a management tool like netsh wfp or Microsoft's WFP API in user mode where the transaction context got lost due to a thread switch or async callback.

The 30-Second Fix: Check Your Transaction Nesting

The simplest cause: you called a commit or abort twice, or you never called begin. What's actually happening here is that transaction handles aren't reference-counted like objects — each begin must pair with exactly one commit or abort.

  1. Go to the code that calls FwpsTransactionCommit0 or FwpsTransactionAbort0.
  2. Verify there's a matching FwpsTransactionBegin0 call that completes before you call commit/abort.
  3. If you see two commits without a begin in between — that's your bug. Delete the extra commit.

Example of the broken pattern (C pseudo-code):

NTSTATUS status;
HANDLE txnHandle;

status = FwpsTransactionBegin0(&txnHandle, ...);  // begin once
if (!NT_SUCCESS(status)) return status;

// ... do some work ...

status = FwpsTransactionCommit0(txnHandle);  // commit once
// Oops: someone added another commit later
status = FwpsTransactionCommit0(txnHandle);  // SECOND commit — triggers 0xC022000D

The fix: remove the duplicate commit. The same applies to abort — calling abort twice is the same problem.

The 5-Minute Fix: Check Thread Affinity and Async Context

If the simple check didn't work, the problem is likely that your begin happened on one thread and your commit/abort on another. WFP transactions are thread-relative in kernel mode. If you begin a transaction in a callback that runs at DISPATCH_LEVEL or in a worker thread, the transaction handle is only valid on that same thread.

  1. Add logging or use WinDbg to capture the thread ID where FwpsTransactionBegin0 runs.
  2. Do the same for the commit/abort call.
  3. If the thread IDs differ, you found the issue.

What's actually happening here: WFP uses the calling thread's context to track the transaction. When you switch threads — say, by posting a work item to a system thread — the new thread has no transaction in progress. Calling commit on that thread gives you 0xC022000D because the engine checks the current thread's transaction list, not the handle’s originating thread.

Real-world trigger: You're in a callout's classifyFn which runs at DISPATCH_LEVEL. You call FwpsTransactionBegin0 there, but then queue a work item to a lower IRQL thread. When that work item calls commit, it fails because the transaction belongs to the original DISPATCH_LEVEL thread.

Fix: Keep begin/commit/abort on the same thread. If you need to defer work, capture the handle and pass it back to the same thread via a completion routine or use KeEnterGuardedRegion/KeLeaveGuardedRegion to serialize access.

The 15+ Minute Fix: Refactor Transaction Lifecycle into a Wrapper

If you're still stuck, the problem is architectural: your code has multiple code paths that can call begin/commit/abort, and they're racing or getting out of sync. I've seen this in large WFP drivers where error handling jumps between functions.

  1. Wrap every transaction in a structure like this:
typedef struct _WFP_TXN_CONTEXT {
    HANDLE TransactionHandle;
    BOOLEAN TransactionStarted;
    KSPIN_LOCK Lock;
} WFP_TXN_CONTEXT;

NTSTATUS TxnBegin(WFP_TXN_CONTEXT *ctx) {
    NTSTATUS status = FwpsTransactionBegin0(&ctx->TransactionHandle, ...);
    if (NT_SUCCESS(status)) ctx->TransactionStarted = TRUE;
    return status;
}

NTSTATUS TxnCommit(WFP_TXN_CONTEXT *ctx) {
    if (!ctx->TransactionStarted) return STATUS_FWP_NO_TXN_IN_PROGRESS;
    NTSTATUS status = FwpsTransactionCommit0(ctx->TransactionHandle);
    ctx->TransactionStarted = FALSE;
    return status;
}
  1. Use this wrapper everywhere. The boolean flag prevents double commits.
  2. Add a KeAcquireSpinLock/KeReleaseSpinLock around all calls in the wrapper if multiple threads can access the same transaction context.
  3. Test under stress — use Driver Verifier with transaction checks enabled.

Why this works: the wrapper forces a single code path for begin/commit/abort, and the flag catches the exact condition the error code describes — trying to commit when no transaction is in progress. It also documents the lifecycle clearly for the next developer.

If It's Still Broken: Check the WFP Engine State

Rare, but possible: the WFP engine itself is in a bad state. Reboot the machine. If the error persists, you may be calling a transaction API on a handle that's been closed by another component. Look for FwpsTransactionAbort0 calls in cleanup paths that might run twice — that's the classic pattern that triggers this.

One more thing: on Windows 10 1809 and later, the kernel-mode transaction APIs got stricter. If you're targeting those builds, double-check your DDK version matches. Using an older WDK header that defines different transaction semantics can cause this error at runtime.

Related Errors in Database Errors
0X80041311 Fix Task Scheduler error 0x80041311 (SCHED_E_ACCOUNT_DBASE_CORRUPT) 9002, 824, 823 SQL Server transaction log corrupted — fix it in 3 steps 0X00000429 Fix ERROR_DATABASE_DOES_NOT_EXIST (0X00000429) Fast ERROR 2003 (HY000) Can't connect to MySQL server on 'localhost' (10061)

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.