feat(domain): migrate default swamp-club URL to swamp-club.com#1228
feat(domain): migrate default swamp-club URL to swamp-club.com#1228
Conversation
Switches the CLI's default registry/auth/telemetry endpoints from the legacy swamp.club domain to swamp-club.com. Existing auth.json files carrying https://swamp.club are transparently rewritten on load (and persisted) so users keep their session without re-running auth login. The SWAMP_CLUB_URL env var and DEFAULT_SWAMP_CLUB_URL constant keep their names — only the URL value changed. Consolidates eight previously-duplicated DEFAULT_SERVER_URL constants in src/cli and src/libswamp into a single import of DEFAULT_SWAMP_CLUB_URL from src/domain/auth/auth_credentials.ts so future domain changes land in one place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
- Silent auth.json migration —
AuthRepository.load()rewrites the user's~/.config/swamp/auth.jsonon first read with no log message. This is correct behavior for a domain migration, but if a user has monitoring on that file or is debugging auth issues, the silent write could be surprising. A singlelogger.info("Migrated auth config to swamp-club.com")at the migration site would make this discoverable without being noisy. Not blocking.
Verdict
PASS — all user-facing strings (login prompt, security report notice, extension URL error guidance) consistently use swamp-club.com. JSON serverUrl field shape is unchanged. The transparent credential migration is scoped correctly to the exact legacy literal and leaves custom/self-hosted servers alone.
There was a problem hiding this comment.
CI Security Review
Critical / High
None.
Medium
None.
Low
None.
Verdict
PASS — The only workflow change (auto-response.yml) is a domain-literal migration from swamp.club → swamp-club.com across five string locations (job name, fetch endpoint, error messages, lab URL). No changes to triggers, permissions, action references, expression handling, or secret management. Existing security properties are preserved: user-supplied issue.title/issue.body remain safely serialized via JSON.stringify(), the labIssueNumber integer validation is untouched, secrets are correctly scoped via env:, and actions/github-script@v7 is an acceptable GitHub-owned action pin. Security-neutral change.
There was a problem hiding this comment.
Code Review
Blocking Issues
None.
Suggestions
- Stale skill examples:
.claude/skills/swamp-issue/SKILL.md:55and.claude/skills/swamp-issue/references/output_shapes.md:16still show"serverUrl": "https://swamp.club"in their example JSON output. Not production code, but worth updating for consistency with the migration.
Notes
Well-executed migration. Key things I verified:
- Consolidation is complete: all 8 duplicated
DEFAULT_SERVER_URLconstants are replaced by a singleDEFAULT_SWAMP_CLUB_URLimport fromsrc/domain/auth/auth_credentials.ts. No remainingDEFAULT_SERVER_URLreferences in the codebase. - Migration scoping is correct:
AuthRepository.load()rewrites only the exact legacy literal"https://swamp.club"— custom servers (e.g.,https://staging.swamp.club) are left alone, confirmed by test atauth_repository_test.ts:292. - Migration persists on first read: the write-on-load behavior is tested (
auth_repository_test.ts:263) and documented in the PR body. Subsequent loads skip the rewrite. - Extension boundary respected:
extensions/models/_lib/swamp_club.tscarries its own hardcoded literals and read-time rewrite since it can't import fromsrc/— correct architectural boundary. - DDD alignment: domain constants live in the domain layer (
auth_credentials.ts), persistence migration lives in infrastructure (AuthRepository).LEGACY_SWAMP_CLUB_URLis a proper domain concept. - libswamp import boundary: CLI commands import
DEFAULT_SWAMP_CLUB_URLfrom the domain layer (src/domain/auth/auth_credentials.ts), not from libswamp internals. No violations. - No fire-and-forget promises:
await this.save(parsed)in the migration path is properly awaited. - Test coverage: two new migration tests (rewrite + persist, leave custom alone), plus all existing tests updated to the new domain.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
-
src/infrastructure/persistence/auth_repository.ts:71-74— Migration save failure breaksload()entirelyThe migration logic at line 73 calls
await this.save(parsed)inside the same try/catch that handles the read. Ifsave()throws (e.g. read-only filesystem, disk full, permission denied on a file owned by a different user), the error falls through to the catch block at line 76, which only swallowsDeno.errors.NotFound. Any other error is re-thrown.This means
load()— which previously always succeeded when the file was readable — now throws on a valid, successfully-parsed auth file if the write-back fails. Callers expectload()to returnAuthCredentials | null, not throw on a readable file.Breaking scenario: User runs swamp in a read-only container or on a filesystem mounted
ro. They have a valid legacyauth.json. Before this PR,load()returns their credentials. After this PR,load()throwsDeno.errors.PermissionDenied, and every command that loads auth breaks.Suggested fix: Wrap the save in its own try/catch so the migration is best-effort:
if (parsed.serverUrl === LEGACY_SWAMP_CLUB_URL) { parsed.serverUrl = DEFAULT_SWAMP_CLUB_URL; try { await this.save(parsed); } catch { // Migration persistence is best-effort; the in-memory // rewrite still takes effect for this invocation. } }
Low
-
extensions/models/_lib/swamp_club.ts:308— Inline legacy URL literal instead of shared constantThe extension file duplicates the legacy URL as a string literal (
"https://swamp.club") rather than importingLEGACY_SWAMP_CLUB_URL. The PR description notes this is intentional (extension code cannot import fromsrc/), so it's a maintenance concern rather than a bug — if the legacy domain check ever needs updating, this site could be missed. Not actionable, just noting for awareness. -
Trailing-slash variant not migrated — If any user's
auth.jsoncontains"https://swamp.club/"(with trailing slash), the exact-match migration won't fire. In practice this is unlikely since the CLI always stored the URL without a trailing slash, but a manual edit could produce it.
Verdict
PASS — The domain migration is well-scoped (exact legacy literal only), tests cover the happy path and the custom-server-left-alone case, and the consolidation of duplicated constants is clean. The save-failure-in-load issue (Medium #1) is worth addressing but not a blocker since it only affects the unlikely intersection of legacy auth files and read-only filesystems.
Summary
https://swamp.clubtohttps://swamp-club.com, plus the telemetry endpoint tohttps://telemetry.swamp-club.com.~/.config/swamp/auth.jsonfiles: on load, ifserverUrl === "https://swamp.club"exactly, rewrite to the new domain and re-save. API keys are domain-independent (verified manually), so users keep their session.DEFAULT_SERVER_URLdefinitions insrc/cliandsrc/libswampinto a single import ofDEFAULT_SWAMP_CLUB_URLfromsrc/domain/auth/auth_credentials.tsso future domain changes land in one place.SWAMP_CLUB_URLenv var andDEFAULT_SWAMP_CLUB_URLconstant keep their names — only the URL value changed, so existing CI configs and shell exports keep working.The migration is scoped to the exact legacy literal — custom servers (e.g.
https://staging.swamp.club, self-hosted forks) are left alone.Pre-merge DNS check
swamp-club.comtelemetry.swamp-club.comBoth new hosts are live and reachable.
Notable behavior changes
extensions/models/_lib/swamp_club.ts(issue-lifecycle extension) cannot import fromsrc/, so it carries its ownhttps://swamp-club.comliteral and a parallel read-time rewrite.AuthRepository.loadnow writes to disk on first read of a legacy file, where it previously only read.Test plan
deno checkcleandeno lintcleandeno fmtapplieddeno run test— 4928 passed, 0 faileddeno run compilesucceedsswamp auth loginwrites the new URLauth.jsonwithhttps://swamp.clubis rewritten tohttps://swamp-club.comafter the first command run, andwhoamisucceeds against the new domain🤖 Generated with Claude Code