Wow, I forgot about this completely.
The root cause is deep, but I might as well document it somewhere.
The dynamic loader used by development mode uses the GHC api to
interpret action code. But the GHC api isn't totally independent of
its environment. In fact, there are a *lot* of external options that
control how it behaves.
I looked at several different ways of attempting to get the
environment correct for the compilation process. The only one I could
make work sufficiently in the end involved a compile-time hook with
TH. During compilation, the dynamic loader has TH code that gets the
arguments GHC was executed with. It uses that argument list to
generate the flags needed for the GHC api.
But this is where things get brittle. The GHC api has a lot of
internal state that's not documented. Further, there isn't really a
function to convert an argument list into a configuration object that
you can then tweak. There are a bunch of different sub-parts of the
argument list that are handled in somewhat different ways.
So, I took a look at how cabal was generating argument lists, and
hacked together a few bad heuristics to handle them. What's going on
here is that the new version of cabal generates argument lists
slightly differently, in a way that the heuristics don't handle
properly. There's a pre-existing hack in place to blacklist certain
flags, and it's easy for me to add "-o" to the list. And I should do
that, since it's a minimal change that will make things work again.
But it's still just papering over a hole caused by not having enough
tooling from the GHC api.