Long long ago we decided to use 1-based indexing for HDUs in FITS files, based on the long-time Fortran legacy of FITS files (some say that the F stands for Fortran; they are only technically wrong), and partly on a reading of the cfitsio manual (details at the end of this RFC).
pyfits (and presumably its astropy.io successor), on the other hand, uses 0-indexing which is what the world expects these days.
This RFC proposes that we switch to using 0-indexing. The current code special cases 0 to mean, "the first non-empty HDU" – we'll have to change this default to e.g. -1 as part of this RFC. This change, too, will reduce confusion.
It'll be a minor change to the obs packages that handle LSST-style MEF data (we'll need to do this anyway as part of this summer's obs package cleanup), and possibly to a few other places in the stack but I'd be surprised if it's many – the butler mostly insulates us from this sort of worry.
We need to go into this code anyway, as the special-case code that skips empty PDUs when reading images is applied to reading metadata, which makes it impossible to read LSST test stand headers (well, without falling back on astropy.io which is what obs_comCam does). See
The cfitsio manual says the first HDU is 1 in section 4.3 (https://heasarc.gsfc.nasa.gov/docs/software/fitsio/quick/node8.html)
The first routine moves to the specified absolute HDU number in the FITS file (the first HDU = 1)
but elsewhere they say that cfitsio is 0 indexed e.g. section 10.7
The HDU may be specified either by absolute position number, starting with 0 for the primary array,
Experiment reveals that they are in fact 0-indexed (my guess is that the author of 4.3 was assuming a separate PDU).