diff --git a/py/emitbc.c b/py/emitbc.c
index 624b98dd42ae1ecafe902481956d4d3926c5a343..f99e7032205ab243f5d28ba19c2753dc1a9ed661 100644
--- a/py/emitbc.c
+++ b/py/emitbc.c
@@ -305,8 +305,26 @@ void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) {
     // we store them as full word-sized objects for efficient access in mp_setup_code_state
     // this is the start of the prelude and is guaranteed to be aligned on a word boundary
     {
+        // For a given argument position (indexed by i) we need to find the
+        // corresponding id_info which is a parameter, as it has the correct
+        // qstr name to use as the argument name.  Note that it's not a simple
+        // 1-1 mapping (ie i!=j in general) because of possible closed-over
+        // variables.  In the case that the argument i has no corresponding
+        // parameter we use "*" as its name (since no argument can ever be named
+        // "*").  We could use a blank qstr but "*" is better for debugging.
+        // Note: there is some wasted RAM here for the case of storing a qstr
+        // for each closed-over variable, and maybe there is a better way to do
+        // it, but that would require changes to mp_setup_code_state.
         for (int i = 0; i < scope->num_pos_args + scope->num_kwonly_args; i++) {
-            emit_write_bytecode_prealigned_ptr(emit, MP_OBJ_NEW_QSTR(scope->id_info[i].qst));
+            qstr qst = MP_QSTR__star_;
+            for (int j = 0; j < scope->id_info_len; ++j) {
+                id_info_t *id = &scope->id_info[j];
+                if ((id->flags & ID_FLAG_IS_PARAM) && id->local_num == i) {
+                    qst = id->qst;
+                    break;
+                }
+            }
+            emit_write_bytecode_prealigned_ptr(emit, MP_OBJ_NEW_QSTR(qst));
         }
     }
 
diff --git a/py/emitnative.c b/py/emitnative.c
index ea81270dbc38ef379a047fb775ac4f4478dff169..8acce8323623f93c6501cbec3623593df9c47e58 100644
--- a/py/emitnative.c
+++ b/py/emitnative.c
@@ -796,8 +796,17 @@ STATIC void emit_native_end_pass(emit_t *emit) {
         ASM_DATA(emit->as, 1, emit->code_info_size);
         ASM_ALIGN(emit->as, ASM_WORD_SIZE);
         emit->code_info_size = ASM_GET_CODE_POS(emit->as) - emit->code_info_offset;
+        // see comment in corresponding part of emitbc.c about the logic here
         for (int i = 0; i < emit->scope->num_pos_args + emit->scope->num_kwonly_args; i++) {
-            ASM_DATA(emit->as, ASM_WORD_SIZE, (mp_uint_t)MP_OBJ_NEW_QSTR(emit->scope->id_info[i].qst));
+            qstr qst = MP_QSTR__star_;
+            for (int j = 0; j < emit->scope->id_info_len; ++j) {
+                id_info_t *id = &emit->scope->id_info[j];
+                if ((id->flags & ID_FLAG_IS_PARAM) && id->local_num == i) {
+                    qst = id->qst;
+                    break;
+                }
+            }
+            ASM_DATA(emit->as, ASM_WORD_SIZE, (mp_uint_t)MP_OBJ_NEW_QSTR(qst));
         }
 
         // bytecode prelude: initialise closed over variables
diff --git a/tests/basics/closure_namedarg.py b/tests/basics/closure_namedarg.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c0c451d15066206ecf8ae70f43c3f6edf252f1e
--- /dev/null
+++ b/tests/basics/closure_namedarg.py
@@ -0,0 +1,9 @@
+# test passing named arg to closed-over function
+
+def f():
+    x = 1
+    def g(z):
+        print(x, z)
+    return g
+
+f()(z=42)
diff --git a/tests/cmdline/cmd_showbc.py.exp b/tests/cmdline/cmd_showbc.py.exp
index 723a403a186d59e209679f196aa4cd007b9a3ac1..02e36765420a7e630e0396ddbcce9544f444113f 100644
--- a/tests/cmdline/cmd_showbc.py.exp
+++ b/tests/cmdline/cmd_showbc.py.exp
@@ -354,7 +354,7 @@ File cmdline/cmd_showbc.py, code block '<genexpr>' (descriptor: \.\+, bytecode @
 Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+):
 ########
 \.\+5b
-arg names:  c e
+arg names: * * *
 (N_STATE 6)
 (N_EXC_STACK 0)
   bc=-\\d\+ line=1
@@ -373,7 +373,7 @@ File cmdline/cmd_showbc.py, code block '<listcomp>' (descriptor: \.\+, bytecode
 Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+):
 ########
 \.\+5b
-arg names:  c e
+arg names: * * *
 (N_STATE 7)
 (N_EXC_STACK 0)
   bc=-\\d\+ line=1
@@ -391,7 +391,7 @@ File cmdline/cmd_showbc.py, code block '<dictcomp>' (descriptor: \.\+, bytecode
 Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+):
 ########
 \.\+5b
-arg names:  c e
+arg names: * * *
 (N_STATE 8)
 (N_EXC_STACK 0)
   bc=-\\d\+ line=1
@@ -411,7 +411,7 @@ File cmdline/cmd_showbc.py, code block 'closure' (descriptor: \.\+, bytecode @\.
 Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+):
 ########
 \.\+5b
-arg names: x
+arg names: *
 (N_STATE 4)
 (N_EXC_STACK 0)
   bc=-\\d\+ line=1
@@ -430,7 +430,7 @@ File cmdline/cmd_showbc.py, code block 'f' (descriptor: \.\+, bytecode @\.\+ byt
 Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+):
 ########
 \.\+5b
-arg names: b a
+arg names: * b
 (N_STATE 4)
 (N_EXC_STACK 0)
   bc=-\\d\+ line=1