diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ComposterWrapper.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ComposterWrapper.java
index 5f81ad065..7b4c157a0 100644
--- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ComposterWrapper.java
+++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ComposterWrapper.java
@@ -105,11 +105,12 @@ public class ComposterWrapper extends SnapshotParticipant<Float> {
 			// Play the sound
 			location.world.playSound(null, location.pos, SoundEvents.BLOCK_COMPOSTER_EMPTY, SoundCategory.BLOCKS, 1.0F, 1.0F);
 		} else if (increaseProbability > 0) {
-			boolean increaseSuccessful = location.world.getRandom().nextDouble() < increaseProbability;
+			BlockState state = location.getBlockState();
+			// Always increment on first insert (like vanilla).
+			boolean increaseSuccessful = state.get(ComposterBlock.LEVEL) == 0 || location.world.getRandom().nextDouble() < increaseProbability;
 
 			if (increaseSuccessful) {
 				// Mimic ComposterBlock#addToComposter logic.
-				BlockState state = location.getBlockState();
 				int newLevel = state.get(ComposterBlock.LEVEL) + 1;
 				BlockState newState = state.with(ComposterBlock.LEVEL, newLevel);
 				location.setBlockState(newState);
diff --git a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java
index b078b2a80..97ee26cec 100644
--- a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java
+++ b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java
@@ -22,6 +22,7 @@ import net.minecraft.block.Block;
 import net.minecraft.block.BlockState;
 import net.minecraft.block.Blocks;
 import net.minecraft.block.ComparatorBlock;
+import net.minecraft.block.ComposterBlock;
 import net.minecraft.block.entity.BrewingStandBlockEntity;
 import net.minecraft.block.entity.ChiseledBookshelfBlockEntity;
 import net.minecraft.block.entity.FurnaceBlockEntity;
@@ -327,4 +328,31 @@ public class VanillaStorageTests {
 
 		context.complete();
 	}
+
+	/**
+	 * Regression test for <a href="https://github.com/FabricMC/fabric/issues/3017">composters not always incrementing their level on the first insert</a>.
+	 */
+	@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
+	public void testComposterFirstInsert(TestContext context) {
+		BlockPos pos = new BlockPos(0, 1, 0);
+
+		ItemVariant carrot = ItemVariant.of(Items.CARROT);
+
+		for (int i = 0; i < 200; ++i) { // Run many times as this can be random.
+			context.setBlockState(pos, Blocks.COMPOSTER.getDefaultState());
+			Storage<ItemVariant> storage = ItemStorage.SIDED.find(context.getWorld(), context.getAbsolutePos(pos), Direction.UP);
+
+			try (Transaction tx = Transaction.openOuter()) {
+				if (storage.insert(carrot, 1, tx) != 1) {
+					context.throwPositionedException("Carrot should have been inserted", pos);
+				}
+
+				tx.commit();
+			}
+
+			context.checkBlockState(pos, state -> state.get(ComposterBlock.LEVEL) == 1, () -> "Composter should have level 1");
+		}
+
+		context.complete();
+	}
 }