The retirement path in the portal's sync_matrix_space removes a retired identity from only two rooms.
/app/code/app/main.py L1456 (in the deployed image) does:
for room in [MATRIX_SPACE_ID] + ([MATRIX_GENERAL_ROOM] if MATRIX_GENERAL_ROOM else []):
which is the old hardcoded pair. The invite path in the same function was updated to member_invite_rooms(), which reads MATRIX_INVITE_ROOMS (currently 7 rooms, Skillshare included) — so the two halves of the function now disagree.
Effect: when a member moves to an external homeserver and their local account is retired, the local identity is kicked from the Space and General room but remains a member of the other five configured rooms, including member-only community rooms. It keeps a lingering account in rooms it should have left.
Fix: use member_invite_rooms() in the retirement loop too, so the invite set and the retire set are the same list by construction rather than by coincidence.
Found while verifying the Skillshare invite fix (#21).
The retirement path in the portal's `sync_matrix_space` removes a retired identity from only two rooms.
`/app/code/app/main.py` L1456 (in the deployed image) does:
for room in [MATRIX_SPACE_ID] + ([MATRIX_GENERAL_ROOM] if MATRIX_GENERAL_ROOM else []):
which is the *old* hardcoded pair. The invite path in the same function was updated to `member_invite_rooms()`, which reads `MATRIX_INVITE_ROOMS` (currently 7 rooms, Skillshare included) — so the two halves of the function now disagree.
**Effect:** when a member moves to an external homeserver and their local account is retired, the local identity is kicked from the Space and General room but remains a member of the other five configured rooms, including member-only community rooms. It keeps a lingering account in rooms it should have left.
**Fix:** use `member_invite_rooms()` in the retirement loop too, so the invite set and the retire set are the same list by construction rather than by coincidence.
Found while verifying the Skillshare invite fix (#21).
What changed (app/main.py, retirement half of sync_matrix_space)
for room in [MATRIX_SPACE_ID] + ([MATRIX_GENERAL_ROOM] if MATRIX_GENERAL_ROOM else []): -> for room in member_invite_rooms():
Retirement logic itself is untouched: kick for joined accounts, m.room.member leave state-event for invite-state accounts.
Commits a89c464 (fix) + 5da1981 (manifest pin), pushed to origin/main.
Image registry.inference.coop/code/member-portal:20261104-a89c464 built and deployed (cloudron update --force). App portal.inference.coop state=installed running health=healthy, /health 200.
Evidence
Deployed /app/code/app/main.py retirement loop now reads for room in member_invite_rooms(): (was the hardcoded pair). The invite loop uses the same call, so the two sets are now the same list by construction.
Calling the deployed member_invite_rooms() inside the running container returns 7 rooms; the old retirement set was 2:
The five newly covered rooms (Design, Community, Infrastructure, Updates, Skillshare) are exactly the leak described in this issue.
Not tested / limits of this verification
No real member identity was retired (out of scope and forbidden), so the actual kick / invite-revocation against a live joined account was not exercised end to end. What is proven is the code path and the resolved room set, not a live retirement event.
The joined / invited guards are still derived from the Space's membership only, so a local account that is joined in a non-Space room but not the Space would not hit the kick branch. Left as-is deliberately to keep this a minimal room-set fix; flagging it rather than changing behaviour silently.
Closing: the code path is deployed and verified.
Fixed and deployed.
**What changed** (`app/main.py`, retirement half of `sync_matrix_space`)
- `for room in [MATRIX_SPACE_ID] + ([MATRIX_GENERAL_ROOM] if MATRIX_GENERAL_ROOM else []):` -> `for room in member_invite_rooms():`
- Retirement logic itself is untouched: kick for joined accounts, `m.room.member` leave state-event for invite-state accounts.
- Commits `a89c464` (fix) + `5da1981` (manifest pin), pushed to `origin/main`.
- Image `registry.inference.coop/code/member-portal:20261104-a89c464` built and deployed (`cloudron update --force`). App portal.inference.coop state=installed running health=healthy, `/health` 200.
**Evidence**
- Deployed `/app/code/app/main.py` retirement loop now reads `for room in member_invite_rooms():` (was the hardcoded pair). The invite loop uses the same call, so the two sets are now the same list by construction.
- Calling the deployed `member_invite_rooms()` inside the running container returns **7** rooms; the old retirement set was **2**:
- `!FKOD2sSqeYvEq9jzr8jDL9ogf3TdJCEI1k4IsnBZsCM` Inference Cooperative (Space)
- `!7Lc8VDphwcipyQ5p5eJAkE-DklqbXTF_gT_yxFDVzyU` General
- `!aiB7xQXLhdYu_DR_Lw4Fwz21USUUjLwuYV8uW8MGAqA` Design
- `!-nDaMBUYsNCVnRRb4W-Z2rZMvy3eOoXntqX96uaqw_w` Community
- `!YHXN-nUr-A7JncEjbc8b6lUHeuMSZw5TGktC8sdnUT4` Infrastructure
- `!gqIkXLuQECcJnlZFFZzXa3COY6AaXtW4KBOWDnyiTZE` Updates
- `!NSPfCdVP7TWTtFoAz1TxVOcqnoDIpMoJDiPb_XS79ys` Skillshare
- The five newly covered rooms (Design, Community, Infrastructure, Updates, Skillshare) are exactly the leak described in this issue.
**Not tested / limits of this verification**
- No real member identity was retired (out of scope and forbidden), so the actual kick / invite-revocation against a live joined account was not exercised end to end. What is proven is the code path and the resolved room set, not a live retirement event.
- The `joined` / `invited` guards are still derived from the Space's membership only, so a local account that is joined in a non-Space room but *not* the Space would not hit the kick branch. Left as-is deliberately to keep this a minimal room-set fix; flagging it rather than changing behaviour silently.
Closing: the code path is deployed and verified.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The retirement path in the portal's
sync_matrix_spaceremoves a retired identity from only two rooms./app/code/app/main.pyL1456 (in the deployed image) does:which is the old hardcoded pair. The invite path in the same function was updated to
member_invite_rooms(), which readsMATRIX_INVITE_ROOMS(currently 7 rooms, Skillshare included) — so the two halves of the function now disagree.Effect: when a member moves to an external homeserver and their local account is retired, the local identity is kicked from the Space and General room but remains a member of the other five configured rooms, including member-only community rooms. It keeps a lingering account in rooms it should have left.
Fix: use
member_invite_rooms()in the retirement loop too, so the invite set and the retire set are the same list by construction rather than by coincidence.Found while verifying the Skillshare invite fix (#21).
Fixed and deployed.
What changed (
app/main.py, retirement half ofsync_matrix_space)for room in [MATRIX_SPACE_ID] + ([MATRIX_GENERAL_ROOM] if MATRIX_GENERAL_ROOM else []):->for room in member_invite_rooms():m.room.memberleave state-event for invite-state accounts.a89c464(fix) +5da1981(manifest pin), pushed toorigin/main.registry.inference.coop/code/member-portal:20261104-a89c464built and deployed (cloudron update --force). App portal.inference.coop state=installed running health=healthy,/health200.Evidence
/app/code/app/main.pyretirement loop now readsfor room in member_invite_rooms():(was the hardcoded pair). The invite loop uses the same call, so the two sets are now the same list by construction.member_invite_rooms()inside the running container returns 7 rooms; the old retirement set was 2:!FKOD2sSqeYvEq9jzr8jDL9ogf3TdJCEI1k4IsnBZsCMInference Cooperative (Space)!7Lc8VDphwcipyQ5p5eJAkE-DklqbXTF_gT_yxFDVzyUGeneral!aiB7xQXLhdYu_DR_Lw4Fwz21USUUjLwuYV8uW8MGAqADesign!-nDaMBUYsNCVnRRb4W-Z2rZMvy3eOoXntqX96uaqw_wCommunity!YHXN-nUr-A7JncEjbc8b6lUHeuMSZw5TGktC8sdnUT4Infrastructure!gqIkXLuQECcJnlZFFZzXa3COY6AaXtW4KBOWDnyiTZEUpdates!NSPfCdVP7TWTtFoAz1TxVOcqnoDIpMoJDiPb_XS79ysSkillshareNot tested / limits of this verification
joined/invitedguards are still derived from the Space's membership only, so a local account that is joined in a non-Space room but not the Space would not hit the kick branch. Left as-is deliberately to keep this a minimal room-set fix; flagging it rather than changing behaviour silently.Closing: the code path is deployed and verified.