Removing the club you are currently using produces no error and no warning at any layer — and the next bare command runs a real job against the club you just disowned.
Chain:
Dispatcher.scala:212-215 (ClubsRemove) deletes the marker and prints. It never reads currentClub, despite runCommand receiving it (Dispatcher.scala:93).
- Server-side only
managed_club + that club's job_schedule rows are removed (ManagedClubRoutes.scala:50-62). The club row survives — the FK is ON DELETE RESTRICT with club as parent.
ClubResolver.single does zero validation and returns the stale current_club verbatim (ClubResolver.scala:22-26).
- Job submission gates on
Club.selectBySlug, not managed status (JobRoutes.scala:428-435). The club row is still there, so the lookup succeeds and the job runs.
User-visible result: after ccas club remove team-alpha, a bare ccas membership / history / recruit / stats submits and runs a full job against team-alpha — Chess.com API traffic and DB mutation. For ccas recruit --stdout this is destructive: it auto-confirms invites (Dispatcher.scala:137), burning real candidates on a disowned club. Nothing in the output hints at it.
Sibling inconsistency: --all correctly excludes the removed club (it reads the live managed set, ClubResolver.scala:37), while the bare/current_club path includes it. Same club, two targeting modes, opposite results.
Also: current_club can't be cleared at all. ConfigWriter exposes only setCurrentClub (no clear/unset), UseClub rejects a blank slug with exit 2 (UseClub.scala:17-19, pinned by TestUseClub.scala:11-13), and ccas config unset targets the other file.
Proposal — auto-clear and say so. Warn-only leaves the stale pointer live; blocking the removal is wrong (the user's intent is clear).
- Add
ConfigWriter.clearCurrentClub — reuse the existing CurrentClubAssignment regex and the atomic temp+rename.
- In the
ClubsRemove branch, compare the removed slug to currentClub case-insensitively after .trim; on a match, clear and print that the current club was cleared.
- Surface the primitive as
ccas use-club --clear for manual fixes.
The next bare command then fails loudly with the existing, already-good ClubResolver.NoClubError.
No test covers any of this — TestClubResolver.scala:23-43 covers precedence among present values only.
Durable fix is the server-side managed gate, filed separately.
Removing the club you are currently using produces no error and no warning at any layer — and the next bare command runs a real job against the club you just disowned.
Chain:
Dispatcher.scala:212-215(ClubsRemove) deletes the marker and prints. It never readscurrentClub, despiterunCommandreceiving it (Dispatcher.scala:93).managed_club+ that club'sjob_schedulerows are removed (ManagedClubRoutes.scala:50-62). Theclubrow survives — the FK isON DELETE RESTRICTwithclubas parent.ClubResolver.singledoes zero validation and returns the stalecurrent_clubverbatim (ClubResolver.scala:22-26).Club.selectBySlug, not managed status (JobRoutes.scala:428-435). The club row is still there, so the lookup succeeds and the job runs.User-visible result: after
ccas club remove team-alpha, a bareccas membership/history/recruit/statssubmits and runs a full job againstteam-alpha— Chess.com API traffic and DB mutation. Forccas recruit --stdoutthis is destructive: it auto-confirms invites (Dispatcher.scala:137), burning real candidates on a disowned club. Nothing in the output hints at it.Sibling inconsistency:
--allcorrectly excludes the removed club (it reads the live managed set,ClubResolver.scala:37), while the bare/current_clubpath includes it. Same club, two targeting modes, opposite results.Also:
current_clubcan't be cleared at all.ConfigWriterexposes onlysetCurrentClub(no clear/unset),UseClubrejects a blank slug with exit 2 (UseClub.scala:17-19, pinned byTestUseClub.scala:11-13), andccas config unsettargets the other file.Proposal — auto-clear and say so. Warn-only leaves the stale pointer live; blocking the removal is wrong (the user's intent is clear).
ConfigWriter.clearCurrentClub— reuse the existingCurrentClubAssignmentregex and the atomic temp+rename.ClubsRemovebranch, compare the removed slug tocurrentClubcase-insensitively after.trim; on a match, clear and print that the current club was cleared.ccas use-club --clearfor manual fixes.The next bare command then fails loudly with the existing, already-good
ClubResolver.NoClubError.No test covers any of this —
TestClubResolver.scala:23-43covers precedence among present values only.Durable fix is the server-side managed gate, filed separately.