Skip to content

[BUGFIX] Regression [Ember 5.12+] Non helpful error when the function for on modifier was forgot as param#21108

Open
johanrd wants to merge 6 commits intoemberjs:mainfrom
johanrd:fix/20783
Open

[BUGFIX] Regression [Ember 5.12+] Non helpful error when the function for on modifier was forgot as param#21108
johanrd wants to merge 6 commits intoemberjs:mainfrom
johanrd:fix/20783

Conversation

@johanrd
Copy link
Contributor

@johanrd johanrd commented Feb 21, 2026

Fixes #20783 / #21045

Since Ember 5.12, passing a non-function to the {{on}} modifier produces an unhelpful TypeError: Cannot read properties of undefined (reading 'bind') instead of the previous helpful message: "You must pass a function as the second argument to the on modifier; you passed undefined."

Root cause

The glimmer-vm refactor in glimmerjs/glimmer-vm@2d9fc01 replaced expect() (a runtime check that always executes) with localAssert() (guarded by LOCAL_DEBUG, which is stripped in published builds). This meant the callback validation became a no-op in published ember-source, while the .bind() call further down — guarded by DEBUG — still runs, producing the unhelpful TypeError.

The key distinction: LOCAL_DEBUG is only true when developing the ember.js repo itself. DEBUG (from @glimmer/env) is true in user app development mode. The existing test suite always runs with LOCAL_DEBUG=true, so this gap was never caught.

Fix

  • Replace localAssert() with an explicit if (DEBUG) check, matching the pattern already used in the same function for positional argument validation
  • Add a smoke test that runs against built ember-source (LOCAL_DEBUG=false, DEBUG=true) — the exact environment users have — to prevent this class of regression

Cowritten by Claude.

Not sure whether it should be backported to LTS, considering the repo-merge.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 21, 2026

Estimated Asset Sizes

Diff

--- main/out.txt	2026-02-17 19:37:18.000000000 +0000
+++ pr/./pr-22260873684/out.txt	2026-02-21 17:14:28.000000000 +0000
@@ -1,62 +1,62 @@
-╔═══════╤═══════════╤═══════════╗
-║       │ Min       │ Gzip      ║
-╟───────┼───────────┼───────────╢
-║ Total │ 351.99 KB │ 203.84 KB ║
-╚═══════╧═══════════╧═══════════╝
+╔═══════╤═══════════╤══════════╗
+║       │ Min       │ Gzip     ║
+╟───────┼───────────┼──────────╢
+║ Total │ 351.99 KB │ 203.7 KB ║
+╚═══════╧═══════════╧══════════╝
 
-╔══════════════════════╤═══════════╤═══════════╗
-║ @ember/*             │ Min       │ Gzip      ║
-╟──────────────────────┼───────────┼───────────╢
-║ Total                │ 313.39 KB │ 181.91 KB ║
-╟──────────────────────┼───────────┼───────────╢
-║ -internals           │ 36.65 KB  │ 26.22 KB  ║
-║ application          │ 13.23 KB  │ 8.05 KB   ║
-║ array                │ 13.01 KB  │ 7.46 KB   ║
-║ canary-features      │ 304 B     │ 389 B     ║
-║ component            │ 2.05 KB   │ 1.64 KB   ║
-║ controller           │ 1.96 KB   │ 1.41 KB   ║
-║ debug                │ 11.69 KB  │ 8.12 KB   ║
-║ deprecated-features  │ 31 B      │ 77 B      ║
-║ destroyable          │ 561 B     │ 383 B     ║
-║ enumerable           │ 259 B     │ 387 B     ║
-║ helper               │ 1.08 KB   │ 811 B     ║
-║ instrumentation      │ 2.43 KB   │ 1.79 KB   ║
-║ modifier             │ 1.22 KB   │ 965 B     ║
-║ object               │ 35.94 KB  │ 22.16 KB  ║
-║ owner                │ 159 B     │ 178 B     ║
-║ renderer             │ 630 B     │ 487 B     ║
-║ routing              │ 59.3 KB   │ 34.12 KB  ║
-║ runloop              │ 2.36 KB   │ 1.5 KB    ║
-║ service              │ 1 KB      │ 845 B     ║
-║ template             │ 654 B     │ 541 B     ║
-║ template-compilation │ 429 B     │ 366 B     ║
-║ template-compiler    │ 123.08 KB │ 59.45 KB  ║
-║ template-factory     │ 370 B     │ 374 B     ║
-║ test                 │ 923 B     │ 627 B     ║
-║ utils                │ 4.11 KB   │ 3.6 KB    ║
-║ version              │ 55 B      │ 131 B     ║
-╚══════════════════════╧═══════════╧═══════════╝
+╔══════════════════════╤═══════════╤══════════╗
+║ @ember/*             │ Min       │ Gzip     ║
+╟──────────────────────┼───────────┼──────────╢
+║ Total                │ 313.39 KB │ 181.9 KB ║
+╟──────────────────────┼───────────┼──────────╢
+║ -internals           │ 36.65 KB  │ 26.23 KB ║
+║ application          │ 13.23 KB  │ 8 KB     ║
+║ array                │ 13.01 KB  │ 7.46 KB  ║
+║ canary-features      │ 304 B     │ 389 B    ║
+║ component            │ 2.05 KB   │ 1.61 KB  ║
+║ controller           │ 1.96 KB   │ 1.41 KB  ║
+║ debug                │ 11.69 KB  │ 8.12 KB  ║
+║ deprecated-features  │ 31 B      │ 77 B     ║
+║ destroyable          │ 561 B     │ 383 B    ║
+║ enumerable           │ 259 B     │ 387 B    ║
+║ helper               │ 1.08 KB   │ 832 B    ║
+║ instrumentation      │ 2.43 KB   │ 1.79 KB  ║
+║ modifier             │ 1.22 KB   │ 963 B    ║
+║ object               │ 35.94 KB  │ 22.16 KB ║
+║ owner                │ 159 B     │ 178 B    ║
+║ renderer             │ 630 B     │ 515 B    ║
+║ routing              │ 59.3 KB   │ 34.14 KB ║
+║ runloop              │ 2.36 KB   │ 1.5 KB   ║
+║ service              │ 1 KB      │ 845 B    ║
+║ template             │ 654 B     │ 530 B    ║
+║ template-compilation │ 429 B     │ 366 B    ║
+║ template-compiler    │ 123.08 KB │ 59.45 KB ║
+║ template-factory     │ 370 B     │ 374 B    ║
+║ test                 │ 923 B     │ 627 B    ║
+║ utils                │ 4.11 KB   │ 3.6 KB   ║
+║ version              │ 55 B      │ 131 B    ║
+╚══════════════════════╧═══════════╧══════════╝
 
-╔═════════════════╤══════════╤══════════╗
-║ @glimmer/*      │ Min      │ Gzip     ║
-╟─────────────────┼──────────┼──────────╢
-║ Total           │ 38.6 KB  │ 21.94 KB ║
-╟─────────────────┼──────────┼──────────╢
-║ destroyable     │ 2.77 KB  │ 1.39 KB  ║
-║ encoder         │ 81 B     │ 171 B    ║
-║ env             │ 38 B     │ 87 B     ║
-║ global-context  │ 886 B    │ 545 B    ║
-║ manager         │ 977 B    │ 608 B    ║
-║ node            │ 175 B    │ 260 B    ║
-║ opcode-compiler │ 1.11 KB  │ 894 B    ║
-║ owner           │ 159 B    │ 202 B    ║
-║ program         │ 252 B    │ 301 B    ║
-║ reference       │ 548 B    │ 531 B    ║
-║ runtime         │ 10.32 KB │ 5.32 KB  ║
-║ tracking        │ 1.34 KB  │ 1.16 KB  ║
-║ util            │ 1.94 KB  │ 1.68 KB  ║
-║ validator       │ 15.75 KB │ 6.96 KB  ║
-║ vm              │ 495 B    │ 569 B    ║
-║ wire-format     │ 1.84 KB  │ 1.35 KB  ║
-╚═════════════════╧══════════╧══════════╝
+╔═════════════════╤══════════╤═════════╗
+║ @glimmer/*      │ Min      │ Gzip    ║
+╟─────────────────┼──────────┼─────────╢
+║ Total           │ 38.6 KB  │ 21.8 KB ║
+╟─────────────────┼──────────┼─────────╢
+║ destroyable     │ 2.77 KB  │ 1.39 KB ║
+║ encoder         │ 81 B     │ 171 B   ║
+║ env             │ 38 B     │ 87 B    ║
+║ global-context  │ 886 B    │ 545 B   ║
+║ manager         │ 977 B    │ 608 B   ║
+║ node            │ 175 B    │ 246 B   ║
+║ opcode-compiler │ 1.11 KB  │ 894 B   ║
+║ owner           │ 159 B    │ 202 B   ║
+║ program         │ 252 B    │ 301 B   ║
+║ reference       │ 548 B    │ 531 B   ║
+║ runtime         │ 10.32 KB │ 5.2 KB  ║
+║ tracking        │ 1.34 KB  │ 1.16 KB ║
+║ util            │ 1.94 KB  │ 1.68 KB ║
+║ validator       │ 15.75 KB │ 6.96 KB ║
+║ vm              │ 495 B    │ 569 B   ║
+║ wire-format     │ 1.84 KB  │ 1.35 KB ║
+╚═════════════════╧══════════╧═════════╝
 

Details

This PRmain
╔═══════╤═══════════╤══════════╗
║       │ Min       │ Gzip     ║
╟───────┼───────────┼──────────╢
║ Total │ 351.99 KB │ 203.7 KB ║
╚═══════╧═══════════╧══════════╝

╔══════════════════════╤═══════════╤══════════╗
║ @ember/*             │ Min       │ Gzip     ║
╟──────────────────────┼───────────┼──────────╢
║ Total                │ 313.39 KB │ 181.9 KB ║
╟──────────────────────┼───────────┼──────────╢
║ -internals           │ 36.65 KB  │ 26.23 KB ║
║ application          │ 13.23 KB  │ 8 KB     ║
║ array                │ 13.01 KB  │ 7.46 KB  ║
║ canary-features      │ 304 B     │ 389 B    ║
║ component            │ 2.05 KB   │ 1.61 KB  ║
║ controller           │ 1.96 KB   │ 1.41 KB  ║
║ debug                │ 11.69 KB  │ 8.12 KB  ║
║ deprecated-features  │ 31 B      │ 77 B     ║
║ destroyable          │ 561 B     │ 383 B    ║
║ enumerable           │ 259 B     │ 387 B    ║
║ helper               │ 1.08 KB   │ 832 B    ║
║ instrumentation      │ 2.43 KB   │ 1.79 KB  ║
║ modifier             │ 1.22 KB   │ 963 B    ║
║ object               │ 35.94 KB  │ 22.16 KB ║
║ owner                │ 159 B     │ 178 B    ║
║ renderer             │ 630 B     │ 515 B    ║
║ routing              │ 59.3 KB   │ 34.14 KB ║
║ runloop              │ 2.36 KB   │ 1.5 KB   ║
║ service              │ 1 KB      │ 845 B    ║
║ template             │ 654 B     │ 530 B    ║
║ template-compilation │ 429 B     │ 366 B    ║
║ template-compiler    │ 123.08 KB │ 59.45 KB ║
║ template-factory     │ 370 B     │ 374 B    ║
║ test                 │ 923 B     │ 627 B    ║
║ utils                │ 4.11 KB   │ 3.6 KB   ║
║ version              │ 55 B      │ 131 B    ║
╚══════════════════════╧═══════════╧══════════╝

╔═════════════════╤══════════╤═════════╗
║ @glimmer/*      │ Min      │ Gzip    ║
╟─────────────────┼──────────┼─────────╢
║ Total           │ 38.6 KB  │ 21.8 KB ║
╟─────────────────┼──────────┼─────────╢
║ destroyable     │ 2.77 KB  │ 1.39 KB ║
║ encoder         │ 81 B     │ 171 B   ║
║ env             │ 38 B     │ 87 B    ║
║ global-context  │ 886 B    │ 545 B   ║
║ manager         │ 977 B    │ 608 B   ║
║ node            │ 175 B    │ 246 B   ║
║ opcode-compiler │ 1.11 KB  │ 894 B   ║
║ owner           │ 159 B    │ 202 B   ║
║ program         │ 252 B    │ 301 B   ║
║ reference       │ 548 B    │ 531 B   ║
║ runtime         │ 10.32 KB │ 5.2 KB  ║
║ tracking        │ 1.34 KB  │ 1.16 KB ║
║ util            │ 1.94 KB  │ 1.68 KB ║
║ validator       │ 15.75 KB │ 6.96 KB ║
║ vm              │ 495 B    │ 569 B   ║
║ wire-format     │ 1.84 KB  │ 1.35 KB ║
╚═════════════════╧══════════╧═════════╝
╔═══════╤═══════════╤═══════════╗
║       │ Min       │ Gzip      ║
╟───────┼───────────┼───────────╢
║ Total │ 351.99 KB │ 203.84 KB ║
╚═══════╧═══════════╧═══════════╝

╔══════════════════════╤═══════════╤═══════════╗
║ @ember/*             │ Min       │ Gzip      ║
╟──────────────────────┼───────────┼───────────╢
║ Total                │ 313.39 KB │ 181.91 KB ║
╟──────────────────────┼───────────┼───────────╢
║ -internals           │ 36.65 KB  │ 26.22 KB  ║
║ application          │ 13.23 KB  │ 8.05 KB   ║
║ array                │ 13.01 KB  │ 7.46 KB   ║
║ canary-features      │ 304 B     │ 389 B     ║
║ component            │ 2.05 KB   │ 1.64 KB   ║
║ controller           │ 1.96 KB   │ 1.41 KB   ║
║ debug                │ 11.69 KB  │ 8.12 KB   ║
║ deprecated-features  │ 31 B      │ 77 B      ║
║ destroyable          │ 561 B     │ 383 B     ║
║ enumerable           │ 259 B     │ 387 B     ║
║ helper               │ 1.08 KB   │ 811 B     ║
║ instrumentation      │ 2.43 KB   │ 1.79 KB   ║
║ modifier             │ 1.22 KB   │ 965 B     ║
║ object               │ 35.94 KB  │ 22.16 KB  ║
║ owner                │ 159 B     │ 178 B     ║
║ renderer             │ 630 B     │ 487 B     ║
║ routing              │ 59.3 KB   │ 34.12 KB  ║
║ runloop              │ 2.36 KB   │ 1.5 KB    ║
║ service              │ 1 KB      │ 845 B     ║
║ template             │ 654 B     │ 541 B     ║
║ template-compilation │ 429 B     │ 366 B     ║
║ template-compiler    │ 123.08 KB │ 59.45 KB  ║
║ template-factory     │ 370 B     │ 374 B     ║
║ test                 │ 923 B     │ 627 B     ║
║ utils                │ 4.11 KB   │ 3.6 KB    ║
║ version              │ 55 B      │ 131 B     ║
╚══════════════════════╧═══════════╧═══════════╝

╔═════════════════╤══════════╤══════════╗
║ @glimmer/*      │ Min      │ Gzip     ║
╟─────────────────┼──────────┼──────────╢
║ Total           │ 38.6 KB  │ 21.94 KB ║
╟─────────────────┼──────────┼──────────╢
║ destroyable     │ 2.77 KB  │ 1.39 KB  ║
║ encoder         │ 81 B     │ 171 B    ║
║ env             │ 38 B     │ 87 B     ║
║ global-context  │ 886 B    │ 545 B    ║
║ manager         │ 977 B    │ 608 B    ║
║ node            │ 175 B    │ 260 B    ║
║ opcode-compiler │ 1.11 KB  │ 894 B    ║
║ owner           │ 159 B    │ 202 B    ║
║ program         │ 252 B    │ 301 B    ║
║ reference       │ 548 B    │ 531 B    ║
║ runtime         │ 10.32 KB │ 5.32 KB  ║
║ tracking        │ 1.34 KB  │ 1.16 KB  ║
║ util            │ 1.94 KB  │ 1.68 KB  ║
║ validator       │ 15.75 KB │ 6.96 KB  ║
║ vm              │ 495 B    │ 569 B    ║
║ wire-format     │ 1.84 KB  │ 1.35 KB  ║
╚═════════════════╧══════════╧══════════╝

@johanrd johanrd force-pushed the fix/20783 branch 2 times, most recently from 674a781 to 680a063 Compare February 21, 2026 12:11
@NullVoxPopuli
Copy link
Contributor

Is the problem still present an canary?

Because: #21091

@johanrd
Copy link
Contributor Author

johanrd commented Feb 21, 2026

@NullVoxPopuli yes, as I understand it, this needs to be tested in smoke tests to fully emulate the user dev mode?

on main(canary), the smoke tests in this PR #21108 (which run with LOCAL_DEBUG=false, DEBUG=true, matching real user dev mode) confirm this. On main/canary:

  not ok 8 - on modifier | error handling: throws helpful error when callback is missing                                                                                                                                                      
      message: Expected helpful error message, got: Cannot read properties of undefined (reading 'bind')                                                                                                                                      
                                                                                                                                                                                                                                              
  not ok 9 - on modifier | error handling: throws helpful error when event name is missing                                                                                                                                                    
      message: Expected helpful error message, got: Cannot destructure property 'tag' of 'ref' as it is undefined.

PR #21091 improved error messages inside check() and localAssert(), but both are gated by LOCAL_DEBUG (not DEBUG), so they're no-ops in published builds. Users still get unhelpful TypeErrors. The fix in fix/20783 correctly replaces localAssert() with if (DEBUG) { throw ... }.


if (DEBUG && !arg0) {
throw new Error(
'You must pass a valid DOM event name as the first argument to the `on` modifier'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we say what was received instead? Can we provide even more information in the error? maybe the element tag name? (at least since it's kinda hard right now to find what element a modifier is on rn)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, good idea.

Sentry reports something like button.font-bold.hover:bg-white.focus-m-1[type="submit"]

Even better would maybe be to use element.cloneNode(false).outerHTML which would print the whole node as in the dom, like <button type="submit" class="font-bold hover:bg-white" id="save">

It may be too long, but that may not be an issue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does sentry get that?

I do think it would b e too long to include the HTML

Copy link
Contributor Author

@johanrd johanrd Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh, checked now, Sentry's _htmlElementAsString builds a compact CSS-selector-style representation: button#save.btn.primary[type="submit"]. See their implementation: https://github.com/getsentry/sentry-javascript/blob/b3d336c98ee58f124004debf2e6aca60365d15b4/packages/core/src/utils/browser.ts#L68-L122

It is quite complex, though. element.cloneNode(false).outerHTML is simpler and gives the full opening tag from the DOM, but it can get verbose with data attributes, analytics attributes etc. But is that an issue in dev mode? alternatively substring it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are they getting access to the element at all tho? 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including html is questionable -- you don't want to leak any data from an app into an exception

Copy link
Contributor Author

@johanrd johanrd Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kategengler ok, yes. something like this would be safe i guess:

You must pass a valid DOM event name as the first argument to the `on` modifier, 
but you passed undefined. Usage example: {{on "click" this.handleClick}}. (Element: <button>)

Adding the id and/or the class list would sometimes be helpful, but potentially leaking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed an update now with the tag name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the classlist is probably fine. Sentry doesn't hide the classes.

Copy link
Contributor Author

@johanrd johanrd Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, added the id and class list now in css selector / sentry style: button#save.btn.primary

johanrd added 4 commits March 7, 2026 14:19
…e? so that way we aren't defining two variables that have the same value?
Show which element the {{on}} modifier is attached to, helping locate
the issue when modifiers are forwarded via ...attributes.
NullVoxPopuli
NullVoxPopuli previously approved these changes Mar 7, 2026
@johanrd johanrd force-pushed the fix/20783 branch 3 times, most recently from 788bea1 to dddb309 Compare March 7, 2026 18:09

});
`,
'on-modifier-error-test.gjs': `
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one of these tests should probably include the selector text, ya?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, good point,
I added it as its own test now: 4bfdd86

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[5.12] Non helpful error when the function for on modifier was forget to add

3 participants