feat(cli): current-club visibility; scope completion to managed clubs#183
Merged
Conversation
The current club was write-only: nothing printed it, `club remove` left it pointing at a club it had just disowned, and job submission gates on the `club` row rather than managed status — so a bare command kept running real jobs against a club the user had stopped managing. With `recruit --stdout` that was destructive, auto-confirming invites on it. - `use-club` takes an optional slug: bare prints the current club (exit 2 when unset), `--clear` drops it. On a set it makes a bounded best-effort `/api/managed-clubs` fetch to refresh the completion cache and say whether the club is actually managed, degrading to an offline hint when the server does not answer. The config write always lands, so it still works offline. - `club remove` clears `current_club` when it names the removed club, and `club list` marks the current one with `*`. - The completion cache is sourced from the managed set rather than `/api/clubs`, which held 9,762 slugs on a dev machine — nearly all opponent and scouted clubs no command can target. `use-club` captures every positional with `repeat` and validates the arity, because `atMost(1)` silently truncates and zio-cli swallows an option written after a positional; together those made `ccas use-club <slug> --clear` a silent set of the very club being cleared. `CompletionCache` stops collapsing its failure modes into "empty": `readClubs` returns `None` when the cache exists but cannot be read, and `writeClubs` reports whether the write landed, so an unwritable cache directory no longer kills shell completion silently. Its TTL check reads `Clock.instant` so the boundary is testable. The verify probe needs `disconnect` before `timeout`: plain `timeout` awaits the interrupted fiber and zio-http's connect path is uninterruptible with a 30s default, so a nominal 2s bound measured 31s against a black-holed host. Closes #172, #173, #174, #175, #178, #179. Refs #176, #181. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P34RY3oqcPmTNRC5XwFTAS
This was referenced Jul 22, 2026
Closed
'club remove' silently leaves a stale current_club, and jobs still run against the removed club
#174
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The current club was effectively write-only. Nothing printed it,
ccas club removeleft it pointing at a club it had just disowned, and job submission gates on theclubrow rather than managed status — so a bare command kept running real jobs against a club the user had stopped managing. Withrecruit --stdoutthat was destructive, auto-confirming invites on it.use-clubgains show/clear. Bareccas use-clubprints the current club (exit 2 when unset);--cleardrops it. On a set it makes a bounded best-effort/api/managed-clubsfetch to refresh the completion cache and report whether the club is actually managed, degrading to an offline hint when the server doesn't answer. The config write always lands, so it still works offline — which is why it stays a top-level local command rather than moving underclub.club removeclearscurrent_clubwhen it names the removed club;club listmarks the current one with*and warns when it isn't managed (including when nothing is managed)./api/clubs, which held 9,762 slugs on a dev machine — nearly all opponent and scouted clubs no command can target. Invalidated onclub add/removeand on aClub not foundsubmit, which is the shape a Chess.com slug rename takes.Two parsing/robustness fixes found while testing
use-clubnow captures every positional withrepeatand validates the arity:atMost(1)silently truncates extras, and zio-cli swallows an option written after a positional — together those madeccas use-club <slug> --cleara silent set of the very club being cleared. It now fails with the working order.The verify probe needs
disconnectbeforetimeout. Plaintimeoutawaits the interrupted fiber and zio-http's connect path is uninterruptible with a 30s default, so the nominal 2s bound measured 31s against a black-holed host. Now 3.2s (JVM boot + the real bound). The same trap applies to the rest ofDispatcher— filed as #182.CompletionCachestops collapsing failure modes into "empty":readClubsreturnsNonewhen the cache exists but can't be read, andwriteClubsreports whether the write landed, so an unwritable cache directory no longer kills shell completion silently.Fixes
Closes #172, #173, #174, #175, #178, #179.
Partially addresses #176 (cache invalidation + rename hint landed; the structural fix — the server gating on exact
Club.selectBySlugbefore rename recovery can run — is #180) and #181 (theuse-clubwrite-failure surfacing landed;seedClubs/appendJob/invalidatestill fail silently).Untouched: #177, #180, #182.
Testing
1111 tests pass (up from 1089);
CompletionCachehad zero coverage and now has a suite, including forced read/write failures and aTestClock-pinned TTL boundary.Every new behaviour was also driven end-to-end against the staged binary:
use-clubshow / set / clear / unmanaged / offlineclub removeauto-clears current clubapi_urlpreservedclub list*marker, unmanaged warning, empty setClub not found→ invalidate + rename hintAlso fixed a test-hygiene bug:
tempDirregistered only the directory fordeleteOnExit, so it was never empty at exit — 71 leaked trees in/tmp, now zero after a full run.🤖 Generated with Claude Code
https://claude.ai/code/session_01P34RY3oqcPmTNRC5XwFTAS