I asked claude, and the reason is a subtle bug. It is because we are showing the contents of the store at the end of the query. This inadvertenlty duplicates constraints in the store.
You can test this by adding the following to your code:
:- set_prolog_flag(chr_toplevel_show_store,false).
and it will work as expected.
I asked claude about a fix and it suggests replacing duplicate_term with copy_term in code called by $enumerate_constraints:
Update(~/git/swipl-devel/packages/chr/chr_swi.pl)
⎿ Added 1 line, removed 1 line
408 State = state(List2),
409 ( call(Goal),
410 arg(1, State, L),
411 - duplicate_term(Templ, New),
411 + copy_term(Templ, New, _),
412 New = Templ,
413 Cons = [New|_],
414 nb_linkarg(2, L, Cons),
Claude verified the patch fixes the issue and reran the tests which passed successfully.
This also explains Tricky CHR behaviour - #3 by AZH