diff --git a/src/engine/blocks.js b/src/engine/blocks.js
index 5a0bbd965..26943af17 100644
--- a/src/engine/blocks.js
+++ b/src/engine/blocks.js
@@ -862,7 +862,12 @@ class Blocks {
 
         // Is this block a top-level block?
         if (typeof e.newParent === 'undefined') {
-            this._addScript(e.id);
+            // When you plug a block into a shadowed input, Blockly will move the shadow block into the top level before
+            // deleting it. Because we don't delete shadows here, instead keeping them on our blocks, the corresponding
+            // "delete" event will be ignored and the shadow would otherwise be added into the top level.
+            // TODO: Blockly inputs' connections keep their own copies of shadow blocks stashed away too.
+            // We should probably use those instead of duplicating the shadow-saving behavior.
+            if (!this._blocks[e.id].shadow) this._addScript(e.id);
         } else {
             // Remove script, if one exists.
             this._deleteScript(e.id);
diff --git a/test/unit/engine_blocks.js b/test/unit/engine_blocks.js
index f4451aa39..95add389d 100644
--- a/test/unit/engine_blocks.js
+++ b/test/unit/engine_blocks.js
@@ -600,6 +600,98 @@ test('delete inputs', t => {
     t.end();
 });
 
+test('drop block into shadowed input', t => {
+    const b = new Blocks(new Runtime());
+    b.createBlock({
+        id: 'foo',
+        opcode: 'motion_movesteps',
+        inputs: {
+            STEPS: {
+                name: 'STEPS',
+                block: 'shadow',
+                shadow: 'shadow'
+            }
+        },
+        topLevel: true
+    });
+    b.createBlock({
+        id: 'shadow',
+        opcode: 'math_number',
+        inputs: {},
+        fields: {
+            NUM: {
+                name: 'NUM',
+                value: '25'
+            }
+        },
+        shadow: true,
+        topLevel: false
+    });
+
+    b.createBlock({
+        id: 'reporter',
+        opcode: 'looks_size',
+        inputs: {},
+        fields: {},
+        topLevel: true
+    });
+
+    // Mimic the series of events emitted by Blockly when you move a block into a shadowed input
+
+    // 1. Move the shadow block to the top level
+    b.moveBlock({
+        id: 'shadow',
+        oldParent: 'foo',
+        newParent: undefined
+    });
+
+    // 2: Delete the shadow block
+    b.deleteBlock('shadow');
+
+    // 3: Move the new block into the input
+    b.moveBlock({
+        id: 'reporter',
+        newParent: 'foo'
+    });
+
+    t.equals(b._scripts.indexOf('shadow'), -1, 'replaced shadow blocks should not be moved to the top level');
+    t.equals(b._scripts.length, 1);
+
+    // Now mimic the series of events when you move the block out of the shadowed input
+
+    // 1. Move the block back out of the shadowed input
+    b.moveBlock({
+        id: 'reporter',
+        oldParent: 'foo',
+        newParent: undefined
+    });
+
+    // 2. Re-create the shadow block
+    b.createBlock({
+        id: 'shadow',
+        opcode: 'math_number',
+        inputs: {},
+        fields: {
+            NUM: {
+                name: 'NUM',
+                value: '25'
+            }
+        },
+        shadow: true,
+        topLevel: false
+    });
+
+    // 3. Move the shadow block into the input
+    b.moveBlock({
+        id: 'shadow',
+        newParent: 'foo'
+    });
+
+    t.equals(b._scripts.indexOf('shadow'), -1, 're-created shadow blocks should not be moved to the top level');
+    t.equals(b._scripts.length, 2);
+    t.end();
+});
+
 test('updateAssetName function updates name in sound field', t => {
     const b = new Blocks(new Runtime());
     b.createBlock({