Skip to content

fix(inactivity): remove exempt members from the sweep protections - #512

Merged
mchestr merged 1 commit into
mainfrom
fix/inactivity-sweep-ignores-exempt
Nov 2, 2026
Merged

mchestr merged 1 commit into
mainfrom
fix/inactivity-sweep-ignores-exempt

Conversation

@mchestr

@mchestr mchestr commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Summary

The inactivity sweep skipped every member with isExempt set. An exemption is about billing, not use, and a member who joins with an invite is made exempt. So the rule protected most members, and the run found 0 members while the users page showed 4 members not seen in 180 days.

This change removes the exempt skip from the rules, the member query, the preview counts and the docs. Admins, the server owner, role holders, paying members, excluded members and members not found in Tautulli stay protected. To keep an exempt member, exclude the member on their page.

The change does not touch the Stripe revoke guard (evaluateRevokeGuard). That guard still protects exempt members.

WARNING: If automatic removal is on, the first run after this release can remove inactive exempt members. Read the list under Admin > Inactive members before the release. The changelog fragment has an upgrade note.

Tests: npx jest lib/inactivity lib/data/__tests__/inactivity app/admin/inactivity components/admin/inactivity passes (18 suites, 242 tests). tsc and eslint lib/inactivity report no errors.

Checklist

  • Matches the components sheet (/dev/ui); visual diff attached (no visual change except one less preview count)
  • npm run design:check passes; any new allowlist entry in scripts/design-system/allowlist.mjs says why (not run)
  • New interactive elements have a data-testid (none)
  • Golden snapshot changes reviewed by the owner (none)
  • Touches .claude/skills/** or .mcp.json: /security-review run (no)
  • Changes code a project skill describes: the skill is updated (no)

An exemption is about billing, not use. Most members who join with an
invite are exempt, so the rule protected almost everyone from the sweep.
Members who must stay can be excluded on their page.
@claude

claude Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Claude finished @mchestr's task in 17s —— View job


Review of #512: remove exempt members from the sweep protections

The change does what the description says, and I found nothing that blocks a merge. I read the diff and grepped for leftover exempt references. I did not run the tests or the type check.

What I checked

  • isExempt is gone from MEMBER_SELECT, the InactivityMember type, INACTIVITY_SKIPS, SKIP_LABELS and the inactivitySkip chain. Nothing under lib/inactivity, components/admin/inactivity or app/admin/inactivity still uses it outside tests.
  • SKIP_LABELS is a Record<InactivitySkip, string>, so the preview counts and the skip tables follow INACTIVITY_SKIPS and won't leave a stale exempt key.
  • The remaining exempt hits in lib/data/ are assistant-data.ts and admin-users.ts. They cover the subscription column and the assistant's user lookup, not the sweep.
  • The changelog fragment has an "Upgrade notes" section, which the Changelog check requires for operator-facing behaviour. The JSDoc example still passes its own test, because jsdoc-examples.test.ts was updated to match.

Test coverage

  • rules.test.ts adds a case where { ...member(), isExempt: true } is not skipped. The spread keeps the test compiling now that isExempt is off the type.
  • sweep.test.ts keeps isExempt on the row type and now expects the exempt member to qualify and be unshared (qualified goes from 2 to 3). That covers the end-to-end change.

Minor notes (non-blocking)

  1. The upgrade note says to check the list "before the next daily run". Operators who upgrade with automatic removal on may not see it in time. Consider also saying that they can turn automatic removal off until they've reviewed the list. The PR description already warns about this.
  2. rules.test.ts passes the extra isExempt through a spread, which relies on TypeScript allowing the wider object. That is fine, but a comment would help a later reader see why the field is there.
  3. The rules.ts comment says "most members who joined with an invite hold one". That is a claim about behaviour elsewhere. It explains why the rule was wrong, but it will go stale if the invite flow changes, so you could move it to the changelog or PR only.
  4. The PR checklist leaves design:check unchecked ("not run"). Only the preview count and the skip list change, so the risk is low, but CI will run it.

Safety
Admins, the owner, role holders, paying members, excluded members and members not found in Tautulli are still protected, and the Stripe evaluateRevokeGuard is untouched, as intended. Because the first run after release can remove previously protected members, the upgrade note and the PR warning are the right mitigation.

I'd approve this once CI is green.
· branch fix/inactivity-sweep-ignores-exempt

@mchestr
mchestr marked this pull request as ready for review November 2, 2026 19:16
@mchestr
mchestr merged commit 7cfb208 into main Nov 2, 2026
24 checks passed
@mchestr
mchestr deleted the fix/inactivity-sweep-ignores-exempt branch November 2, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant