Unable to render to all mipmap levels in a 2D array texture

I have a 2048x2048 2D texture array with 20 layers, created like so:

int width = 2048;
int height = 2048;
int layers = 20;

uint handle = Gl.GenTexture();
Gl.BindTexture(TextureTarget.Texture2dArray, handle);

int levels = (int)Math.Floor(Math.Log(Math.Max(width, height), 2)) + 1;

Gl.TexImage3D(TextureTarget.Texture2dArray, 0, InternalFormat.Rg8i, width, height, layers, 0, PixelFormat.RgInteger, PixelType.UnsignedByte, IntPtr.Zero);
Gl.TexStorage3D(TextureTarget.Texture2dArray, levels, InternalFormat.Rg8i, width, height, layers);
Gl.BindTexture(TextureTarget.Texture2dArray, 0);

I then use a shader and framebuffer to write data to each level of each layer in the texture array:

// Create a framebuffer
var framebuffer = Gl.GenFramebuffer();

Gl.BindFramebuffer(FramebufferTarget.DrawFramebuffer, framebuffer);
Gl.BindTexture(TextureTarget.Texture2dArray, handle);


// Simple fragment shader that always outputs ivec2(100, 100)
SimpleShader.UseProgram();


for (int layer = 0; layer < layers; layer++)
{
    int width = 2048;
    int height = 2048;

    for (int level = 0; level < levels; level++)
    {
        // Associate
        Gl.FramebufferTextureLayer(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0, handle, level, layer);
        Gl.DrawBuffers(Gl.COLOR_ATTACHMENT0);


        // Validate
        var status = Gl.CheckFramebufferStatus(FramebufferTarget.DrawFramebuffer);
        Debug.Assert(status == FramebufferStatus.FramebufferComplete);


        // Render
        Gl.Viewport(0, 0, width, height);
        RenderFullScreenQuad();

        width = Math.Max(1, width / 2);
        height = Math.Max(1, height / 2);
    }
}


// Unbind everything
...

The issue is that this only writes data to the 0th level of every layer in the texture. Every other level on every layer is empty.

As a sanity check I used glTexSubImage3D to set data on a specific level in the texture and that works great, I can read non-zero data from that level. The above is a very simple example; the shader in practice is a normal map generator, but I’ve simplified the code above for clarity.

Hardcoding the level parameter to 3 (for example) in every Gl.FramebufferTextureLayer call strangely works - every level of every layer is empty, except for the 4th layer. This means it is definitely possible to render to a specific level of the texture array.

I tried detaching the texture after rendering the full screen quad, but no luck.

// Render
...

// Detach
Gl.FramebufferTextureLayer(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0, 0, level, layer);
Gl.DrawBuffers();

// Validate
var status = Gl.CheckFramebufferStatus(FramebufferTarget.DrawFramebuffer);
Debug.Assert(status == FramebufferStatus.FramebufferIncompleteMissingAttachment);

I also tried creating an entire new framebuffer inside the inner-most loop, but no luck either. Any help would be greatly appreciated!