Figured it out. I don't think it was client validation I was seeing either. I'd confused it for that because my server side onclick wasn't being reached. However the problem was actually with recreating my dynamic controls (I.e. the image buttons) on subsequent postbacks - in particular here, the postback initiated by the user clicking the button. It actually bypassed client validation but as I wasn't recreating the buttons on postback it didn't know that the button causing the postback was set to CausesValidation=false so server validation fired. It wasn't hitting the onclick because, again, it hadn't been recreated so didn't know the button causing the postback had an event handler.
Tip of the day, always recreate your dynamic controls on postback!