Based on @shhyou's digging, here's a way to do it by arranging for the #%top-interaction to be different if the module is successfully expanded. A more robust implementation could still allow modules written in the language to define #%top-interaction. You can try this with #lang s-exp.
IIRC DrRacket and Racket Mode already show a message and disable the REPL when #%top-interaction isn't defined at all -- when it's not in namespace-mapped-symbols for a namespace "inside" the module (from module->namespace). Some langs like #lang info (IIRC) do this, always.
So I think it would also work for an expansion conditionally not to define #%top-interaction at all.
Having said that, I think an advantage of your approach is your raise-syntax-error can supply a more precise/informative error message ("no REPL available because your program failed to type check" or whatever). This makes it clearer it's related to the particular user program, not a feature of the module for all user programs.
It has never bothered me, because I misbelieved #lang info was supposed to the moral equivalent of a dictionary or hash-table, where the values are all simple string, number, and list values. At least, that's all that I've done myself, or noticed in examples. Given that, what could the REPL show, that isn't self-evident in the source?
But just now I re-checked the docs and noticed the grammar has a whole section of info-primitive items. So it could be handy to view the resulting values in a REPL, without needing to fire up get-info.
p.s. I suppose the Racket Mode REPL could inject, when missing, a #%top-interaction like the one for racket. But I suspect that would be one of those things where it's 2 minutes to implement the feature itself... and hours to work out how users configure/control/understand when that magic happens, or not.