You're getting 0x1A39 and it's driving you nuts.
I've seen this dozens of times across Windows Server 2008 R2 through 2022, usually when someone's writing custom transaction code against the Kernel Transaction Manager (KTM) or using the PushTransaction and PullTransaction APIs in ClusAPI. The error means the marshall buffer you handed to the API is garbage — wrong format, wrong alignment, or totally malformed.
The fix: flatten your buffer
The culprit here is almost always a buffer that isn't a simple, contiguous byte array. The API expects a TRANSACTION_MARSHALL_BUFFER structure at the start, followed by a flat blob of data. If you're passing a pointer to a complex structure with indirection (like nested pointers or variable-length fields that aren't serialized), the API chokes.
Here's the corrected pattern in C++:
// Correct: flat, contiguous marshall buffer
DWORD bufSize = sizeof(TRANSACTION_MARSHALL_BUFFER) + dataSize;
BYTE* buffer = new BYTE[bufSize];
TRANSACTION_MARSHALL_BUFFER* tmBuf = (TRANSACTION_MARSHALL_BUFFER*)buffer;
tmBuf->MajorVersion = TRANSACTION_MARSHALL_BUFFER_MAJOR_VERSION;
tmBuf->MinorVersion = TRANSACTION_MARSHALL_BUFFER_MINOR_VERSION;
tmBuf->ShutdownLevel = 0;
tmBuf->Reserved = 0;
// Copy your actual data right after the header
memcpy(buffer + sizeof(TRANSACTION_MARSHALL_BUFFER), yourData, dataSize);
// Now call PushTransaction
DWORD result = PushTransaction(transactionHandle, buffer, bufSize, 0, NULL, NULL);
if (result != ERROR_SUCCESS) {
// handle error
}
delete[] buffer;Don't use new in production without proper error handling — that's just to show the pattern. Use std::vector<BYTE> or a smart pointer instead.
The key: the buffer must be a single allocation where the header and data are adjacent in memory. No pointers to separately allocated data. No alignment gaps beyond what the header struct naturally requires.
Why this happens
PushTransaction/PullTransaction serialize the marshall buffer into a kernel-mode structure. The kernel expects a flat copy from user mode — it doesn't chase pointers. If you pass a pointer to a string or a secondary buffer, the kernel reads random garbage or zeros, then rejects it with 0x1A39.
Another common cause: using a packed struct but forgetting that the marshall header has specific alignment requirements. On x64, TRANSACTION_MARSHALL_BUFFER is 20 bytes (4+4+4+4+4), but with default alignment it becomes 24 bytes due to padding. If your code assumes 20 bytes and builds the buffer manually, you'll mismatch the expected size. Always use sizeof on the header, never a hardcoded size.
Less common variations
Sometimes the same error appears when pulling a transaction from a cluster. If you're calling ClusAPI::PullTransaction, the remote transaction may have a different marshall version. Check the MajorVersion and MinorVersion fields match what your node expects. I've seen this between a 2012 R2 and 2016 cluster — the minor version changed.
Another edge case: the buffer size parameter is wrong. The API expects the total size including the header. If you pass only the data size, you get the same error. Double-check your size calculation against the actual allocated buffer.
Also, on older Windows builds (pre-Win8), the Reserved field had to be zero. Any non-zero value would trigger 0x1A39. If you're on Server 2008 R2, zero out the whole header with ZeroMemory before setting the version fields.
How to prevent it
Three rules:
- Always allocate the marshall buffer as one block. Header + data in the same contiguous memory. Use a vector or a single malloc.
- Use sizeof for the header. Never hardcode 20 or 24. Let the compiler figure it out.
- Zero the header before filling it. The reserved fields must be zero.
ZeroMemory(tmBuf, sizeof(TRANSACTION_MARSHALL_BUFFER))before setting version.
If you're using .NET or PowerShell, you're likely going through Win32 interop. Make sure you marshal the buffer as a byte array with sequential layout. The same rules apply: no managed references, no nested objects. Flatten everything to bytes before calling the API.
One last thing: if you're doing this in a clustered environment, test your transaction marshall code in a non-clustered test first. That isolates whether the error is from the buffer format or from cluster-specific version mismatches. Saves hours of head-scratching.