Is there any chance we could instead do something like flushing the current command list in d3d12_runner_destroy_resource() if needed?
In principle yes, of course, but I'm not sure it's worth the effort. Notice that we're already doing something similar for dispatch and draw operations. If we decide that we never want to execute and flush the command list until we need to get back data from the GPU, we'd have to track all the operations that are outstanding at any given moment so we can reply them when flushing as you propose. Or we'd have to keep many lists around and choose which one to fire and when, and possibly also add synchronization between them. My feeling is that both are overkill for a test program which we'd rather try to keep simple and explicit.
What I had in mind would be to set something like "runner->pending_list = GRAPHICS | COMPUTE;" at the end of things like d3d12_runner_dispatch()/d3d12_runner_draw()/d3d12_runner_get_resource_readback(), and then flush as appropriate; it wouldn't need to be terribly complicated.
But yes, keeping track of the resource state might ultimately be nicer.