From be7103374ac369e3f09eb020be204cd055e6ee8e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 6 May 2026 22:25:45 +0000
Subject: [PATCH 1/2] Initial plan
From d9dae91698705064d41d7013b90285a4d90bbaa1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 6 May 2026 22:56:13 +0000
Subject: [PATCH 2/2] Fix UpdateDynamicModifierOpcode: reset tag to null when
instance becomes undefined
Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/c1b037b4-d2f5-447c-b42a-5f5cb9387234
Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
---
.../test/modifiers/dynamic-modifiers-test.ts | 46 +++++++++++++++++++
.../runtime/lib/compiled/opcodes/dom.ts | 7 ++-
2 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/packages/@glimmer-workspace/integration-tests/test/modifiers/dynamic-modifiers-test.ts b/packages/@glimmer-workspace/integration-tests/test/modifiers/dynamic-modifiers-test.ts
index 91854bdbf7e..6607b1204c2 100644
--- a/packages/@glimmer-workspace/integration-tests/test/modifiers/dynamic-modifiers-test.ts
+++ b/packages/@glimmer-workspace/integration-tests/test/modifiers/dynamic-modifiers-test.ts
@@ -87,6 +87,52 @@ class DynamicModifiersResolutionModeTest extends RenderTest {
assert.deepEqual(receivedArgs, ['y', 'x']);
}
+ @test
+ 'Dynamic modifier becoming undefined does not error when args change afterwards'(assert: Assert) {
+ let installCount = 0;
+ let lastValue: unknown;
+
+ // The modifier reads args[0], so its tag tracks the arg's reactivity.
+ // When outer changes and the modifier's tag is invalidated, the bug triggers:
+ // scheduleUpdateModifier would be called with undefined instance.
+ const foo = defineSimpleModifier((element: Element, args: unknown[]) => {
+ installCount++;
+ lastValue = args[0];
+ (element as HTMLElement).dataset['value'] = String(args[0]);
+ });
+
+ this.render(
+ `
+ {{~#let (modifier this.foo this.outer) as |bar|~}}
+
content
+ {{~/let~}}
+ `,
+ { foo, outer: 'initial', cond: true }
+ );
+
+ this.assertHTML('content
');
+ assert.strictEqual(installCount, 1, 'modifier installed once on initial render');
+ assert.strictEqual(lastValue, 'initial');
+
+ // Disable the modifier — newInstance becomes undefined, instance is destroyed
+ // (data-value persists because no cleanup function was returned)
+ this.rerender({ cond: false });
+ this.assertHTML('content
');
+
+ // Change args while the modifier is disabled — this triggered the bug:
+ // "Cannot destructure property 'manager' of '.for' as it is undefined"
+ // because the modifier's tag was invalidated by the arg change, and the
+ // stale tag caused scheduleUpdateModifier to be called with undefined.
+ this.rerender({ outer: 'changed' });
+ this.assertHTML('content
');
+
+ // Re-enable the modifier — should install cleanly with updated args
+ this.rerender({ cond: true });
+ this.assertHTML('content
');
+ assert.strictEqual(installCount, 2, 'modifier installed again after re-enabling');
+ assert.strictEqual(lastValue, 'changed');
+ }
+
@test
'Can pass curried modifier as argument and invoke dynamically (with args)'() {
const foo = defineSimpleModifier(
diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
index e12d7f8fbfb..b6687aa8183 100644
--- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
+++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
@@ -363,11 +363,16 @@ export class UpdateDynamicModifierOpcode implements UpdatingOpcode {
this.tag = tag;
vm.env.scheduleInstallModifier(newInstance);
+ } else {
+ tag = null;
+ this.tag = null;
}
this.instance = newInstance;
} else if (tag !== null && !validateTag(tag, lastUpdated)) {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
+ // tag !== null guarantees instance is defined: this.tag is only set to a
+ // non-null value alongside this.instance being set to a non-null value.
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
vm.env.scheduleUpdateModifier(instance!);
this.lastUpdated = valueForTag(tag);
}