Well, probably nobody is using the password expiration feature, but if anyone was interested the loop login/password-change is caused by the proc acs_user::get using util_memoize to get the user data.
This is useful to avoid repeatedly reading the users table, but when during login the proc auth::check_local_account_status compares the password_age_days with PasswordExpirationDays it uses the cached value and leads to a loop.
The proc auth::check_local_account_status is the only point where the password age is checked and is only used during the login and so I simply reread password_age_days:
set password_age_days [db_string reread "
select trunc(date_part('epoch', age(password_changed_date))/(60*60*24))
from users
where user_id = :user_id"]