You're backing up a SQL Server database to a network share with VSS, or running a TxF-aware file operation, and the job dies with a hex code that looks like a typo: 0xC0190028. Windows Event Viewer logs it as STATUS_HANDLE_NO_LONGER_VALID. Sometimes you see it during a transactional NTFS (TxF) commit, sometimes when a backup agent enumerates files inside a snapshot, and occasionally on a plain MoveFileTransacted call. The common thread is always the same: a file handle existing inside a transaction got closed, and something tried to use it afterward.
What's actually happening here
Windows has a kernel-level transaction manager called KTM (Kernel Transaction Manager). It implements TxF, the transactional filesystem layer on NTFS, and TxR for the registry. When you open a handle inside a transaction — say, with CreateFileTransacted — that handle is bound to the transaction's lifetime. As soon as the transaction commits, rolls back, or gets force-closed, KTM invalidates every handle attached to it. Not just closes them. Invalidates them. The handle value stays in your code, but the kernel marks it dead.
The reason you get STATUS_HANDLE_NO_LONGER_VALID instead of STATUS_INVALID_HANDLE is that the kernel is being specific. It knows the handle was valid. It remembers why it died. And it's telling you the transaction took it with it.
This is different from a plain invalid handle bug. With STATUS_INVALID_HANDLE, you passed garbage. With 0xC0190028, the handle was fine — the transaction it belonged to moved on without you.
The most common real-world trigger I've seen in the field is a backup solution that opens files inside a TxF transaction, commits the transaction to finalize the snapshot metadata, then keeps reading from the handles it opened earlier. On Server 2016 and 2019, this pattern often worked because of timing. On Server 2022 and Windows 11 22H2 and later, the kernel tightens handle invalidation during commit, and the same code fails hard.
The fix
If you control the code, the fix is structural. If you don't, the fix is to make the transaction boundaries match the handle lifetime. Either way, the steps below walk through the diagnosis and repair.
Capture a stack trace at the point of failure. Don't guess. Attach WinDbg or ProcDump to the faulting process and break on
nt!NtSetInformationFileor on the exception itself. You want to see which function passed the handle. In 90% of cases it's a read or write issued afterCommitTransactionorRollbackTransactionreturned.Audit every TxF call in the failing component. Look for this pattern:
HANDLE h = CreateFileTransacted(path, ...); // ... work with h ... CommitTransaction(hTransaction); ReadFile(h, buffer, size, &read, NULL); // boom CloseHandle(h);That
ReadFileafter commit is your bug. The handle is dead the instantCommitTransactionreturns. Move all I/O before the commit, or open a fresh handle outside the transaction for post-commit reads.If you're not the developer, check for a backup/AV agent conflict. Run
fltmc instancesand look for filter drivers layered on the volume. Antivirus minifilters (I've seen this with older Symantec and Trend Micro builds) will sometimes hold transaction-scoped handles and close them out from under the primary process. Disable the filter temporarily viafltmc detach <filtername> <volume>and see if the error clears. If it does, update the filter or add an exclusion for your backup path.Roll back any recent Windows cumulative update on the affected server. I'm serious. The handle invalidation semantics for TxF were tightened in the March 2023 rollups for Server 2022. If your backup ran fine in January and started throwing 0xC0190028 in April with no code change, that's the smoking gun.
wusa /uninstall /kb:<number>and retest. Then open a case with the backup vendor rather than staying unpatched forever.Fall back to non-transactional I/O if TxF isn't a hard requirement. TxF is deprecated. Microsoft has said so since 2018. If your code is using
MoveFileTransactedjust to get atomic rename semantics, useMoveFileExwithMOVEFILE_REPLACE_EXISTINGand a proper journal in your own app. You lose kernel-level atomicity and gain back your sanity.
If it still fails
Turn on KTM tracing. From an elevated prompt:
logman create trace KTM -p Microsoft-Windows-Kernel-TransactionManager 0xffffffffffffffff 5 -o C:\ktm.etl
logman start KTM
:: reproduce the error
logman stop KTM
Open C:\ktm.etl in WPA or tracerpt and look for the TransactionCommit event that fires immediately before the failing handle operation. That timestamp is your dividing line.
Also check whether the volume is a ReFS volume masquerading as NTFS. ReFS doesn't support TxF at all — you'll get STATUS_HANDLE_NO_LONGER_VALID or worse when code assumes transactional semantics are available. fsutil fsinfo refsinfo C:\ tells you which filesystem you're actually on.
Finally, if the failure is inside a service running as LocalSystem and only happens after idle periods, check for a session 0 handle leak. Handles can be duplicated across threads with DuplicateHandle, and the duplicate doesn't inherit the transaction binding. If someone duplicated your TxF handle, the duplicate survives the commit — but any operation through it still fails with 0xC0190028 when the kernel checks back against the (now-dead) transaction. Search the source for DuplicateHandle and audit each call site.
The error code is verbose for a reason. Read it literally: the handle is no longer valid because a transaction invalidated it. Find the transaction. Find the handle. Separate their lifetimes. That's the whole fix.