A gitsocial push is several git pushes: the code branches, the extra branches, the tags, each gitmsg/<ext> branch, then refs/gitmsg/* when a state ref moved. gitmsg.PushWithProgress sets GITSOCIAL_S3_DEFER_MAINTENANCE=1 on every transfer but the last, and the helper runs the post-push pass once, on the last one, with the Updates map of that transfer alone (objstore.remoteHelper.postPushMaintenance).
site.PostPushMaintenance walks out.Updates in updatePushedSiteItems. When the last transfer is refs/gitmsg/*, no gitmsg/<ext> destination is in the map, so no item index advances. The items manifests of the earlier tip are complete, the page manifest agrees with them, and the pass stamps the push-state marker with the digest of the new refs. The site pass of the CLI then reads the marker, prints site up to date, and skips. A later push --site-only skips too. The item indexes and the pages stay at the earlier tip until the marker is removed by hand.
Seen on a mirror refresh of a GitHub project: the full import moved gitmsg/pm, gitmsg/review, gitmsg/release and gitmsg/social and 733 fork refs; the bucket got the refs, the site kept the 200 items of the first batch, and the push printed Site: published. Without a state ref move, the last transfer is the last gitmsg branch, and only that extension advances.
Repair on a bucket: put a marker that reads as absent, for example {"version":0}, at .gitsocial/site/push-state with gitsocial remote put, then gitsocial push --site-only.
Fix options: the deferred pass walks every refs/heads/gitmsg/<ext> tip in out.Refs, as rebuildSiteItems does for the site-only path, which costs one manifest read per extension on an unchanged tip; or the helper passes the union of the Updates of the deferred transfers to the last one. The first keeps the helper free of cross-push state. A test: a push of two extension branches followed by a state ref push advances both indexes and stamps the marker only after.