The cron event that schedules itself twice

wp_schedule_event() does not check whether the hook is already scheduled, so calling it on init — which is where it usually ends up — adds another entry on every request.

add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'shop_nightly_export' ) ) {
        wp_schedule_event( strtotime( 'tomorrow 02:00' ), 'daily', 'shop_nightly_export' );
    }
} );

// and on deactivation
wp_clear_scheduled_hook( 'shop_nightly_export' );

The duplicate entries are invisible until the job runs several times in a row, and wp cron event list is the fastest way to see them. Scheduling on plugin activation rather than on init avoids the problem entirely and has its own failure mode — an activation hook does not fire on an update, so a new schedule added in version 2 never gets registered on an existing install.