Ack! Caught a mistake in the draft code (I’d renamed some variables mid-way but apparently didn’t fix all occurrences). It should go \cs_new_protected:Npn \__keys_filters_set:n #1 { \clist_clear:N \l__key_filters_clist \clist_map_inline:nn {#1} { \prop_if_in:NoTF \l__filter_states_prop { \l__keys_module_tl / ##1 } { \clist_put_right:No \l__key_filters_clist { \l__keys_module_tl / ##1 } } { \__msg_kernel_error:nnoVV { kernel } { invalid-filter } { \l__keys_module_tl / ##1 } \l_keys_path_tl \l__keys_module_tl } } \clist_if_empty:NF \l__key_filters_clist % VARIABLE RENAMED HERE { \prop_put:NVV \l__keys_filters_prop \l_keys_path_tl \l__key_filters_clist } } The several l__tmpX_Y variables I use should also be renamed; for some reason I thought those were defined in addition to the l_tmpX_Y versions.