Your transaction tool reports FAILED_TO_FINALIZE. Your CI marks the job red. Your retry logic fires. But the block explorer says the transaction finalized 22 seconds after you sent it.

The exit code lied.

This is a debugging story from midnight-node — Midnight's Substrate-based node — about how a transaction watcher loses track of a transaction during a chain fork, why the failure is invisible in tests, and the pattern that fixes it: on timeout, ask the chain itself, not the stream that was supposed to tell you about it. The fix landed as PR #1927 for issue #1854.

Forks are normal, your tooling forgets that

Midnight produces blocks with AURA every 6 seconds and finalizes them with GRANDPA a couple of blocks later. Between production and finality, the chain is allowed to disagree with itself: two validators can build competing blocks, and one branch eventually wins. A transaction included in the losing branch is retracted — kicked back to the transaction pool — and normally re-included in a block on the winning branch a few seconds later.