I got an answer from @yallop elsewhere, which is, for posterity:
When using the dynamic interface (which dynlinks the C library), the struct names do not matter. If I understand correctly, this means that assigning a dummy name to the anonymous union should work.
Using stub generation, it is not really possible at the moment. The best approach is to fix the C library by adding a tag to the anonymous union. For example, see https://github.com/grpc/grpc/pull/12059, and also https://github.com/grpc/grpc/issues/12075 which explains a bit more why anonymous structures do not work well with ctypes and stubgen.
Edit: (also from @yallop)
One workaround is to add an accessor function in a header file, and add a binding to that function:
/* in the C header file */
inline int *bar(struct a *p) { return &p->b.bar; }
(* in the ctypes bindings *)
let bar = foreign "bar" (ptr (structure "a") @-> returning (ptr int))