h2 ends up being initialized like this inside empty_clone (with explicit bin edges):
>>> h2 = Hist2D([0.0, 0.5, 1.0], [0.0, 0.5, 1.0])
And then ROOT stores the edges in the internal TArrayD.
h1 was initialized with fixed-width bins and ROOT doesn't bother keep the array of bin edges.
Then in TH1::CheckConsistency / TH1::CheckBinLimits the DifferentBinLimits exception is thrown when the lengths of the TArrays don't match:
>>> h1.xaxis.GetXbins().fN
0
>>> h2.xaxis.GetXbins().fN
3
So like you saw:
>>> h2.Add(h1)