fix: token deduplication and stale token accumulation#25
Open
JoshSalway wants to merge 5 commits intolaravel:mainfrom
Open
fix: token deduplication and stale token accumulation#25JoshSalway wants to merge 5 commits intolaravel:mainfrom
JoshSalway wants to merge 5 commits intolaravel:mainfrom
Conversation
When authenticating multiple times, the same API tokens were appended to config.json without deduplication. Each duplicate token triggered a separate API call returning the same organization, causing the org selection prompt to show duplicate entries. Add unique() to both apiTokens() (to handle existing duplicated configs) and addApiToken() (to prevent future duplicates). Fixes laravel#22 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When running `cloud auth` multiple times, old tokens accumulated in
config.json because new tokens were appended without removing previous
ones. Expired/revoked tokens were never cleaned up, causing
`{"message":"Required."}` errors when the CLI tried to use them.
Three fixes:
- Auth command now replaces all tokens with fresh ones from the auth
session instead of appending, preventing stale token buildup
- Token resolution now validates tokens via API before use and
automatically removes expired/invalid ones from config
- Added ConfigRepository::setApiTokens() for bulk token replacement
Closes laravel#23
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The isValidToken() check made an unmocked API call to /api/meta/organization on every command, causing 5 test failures. For a single token, just use it directly — if expired, the actual command will fail with a clear auth error. Token validation is only needed when choosing among multiple tokens. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proves both Issue laravel#22 and laravel#23 fixes with 6 targeted tests: - addApiToken() no longer accumulates duplicates (was 5, now 1) - apiTokens() deduplicates existing bloated configs on read - setApiTokens() atomically replaces all tokens - setApiTokens() deduplicates input - removeApiToken() removes specific tokens - Empty config returns empty collection All 6 tests FAIL on main branch (confirming the bugs exist) and PASS on this branch (confirming the fixes work). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 tasks
Ensures setApiTokens correctly handles users with multiple orgs: - Preserves multiple tokens (one per org) - Atomically replaces all org tokens on re-auth - Handles org count changing between auth sessions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
Fixes two related token management bugs that share a root cause: tokens accumulating in
config.jsonwithout deduplication or cleanup.Closes #22
Closes #23
Issue #22: Duplicate organizations in selection prompt
Root Cause
ConfigRepository::addApiToken()used->push($token)without deduplication. Runningcloud authmultiple times appended the same token repeatedly:Then in
HasAClient::resolveApiToken(), each duplicate token triggers a separate API call to$client->meta()->organization(). With 5 duplicate tokens, the same org appears 5 times in the picker.Proof
Simulating 5 auth sessions with the same token against the original code:
Fix
apiTokens()returns->unique()->values()— deduplicates on read (fixes existing bloated configs)addApiToken()chains->unique()->values()afterpush()— prevents future duplicates on writeIssue #23:
command:runreturns{"message":"Required."}despite valid authRoot Cause
Two interrelated bugs:
1. Token accumulation (
Auth.php) — Eachcloud authsession appended new tokens without removing old ones:After 3 auth sessions:
["token-A-expired", "token-B-expired", "token-C-valid"]2. Unhandled exception on expired tokens (
HasAClient.php) — With multiple tokens,resolveApiToken()iterates all of them calling$client->meta()->organization(). The Connector uses Saloon'sAlwaysThrowOnErrorstrait, so any expired token throws aRequestException— but this was never caught:The unhandled exception bubbles up and the user sees
{"message":"Required."}.Proof
Simulating 3 auth sessions against the original code:
BUG: 3 tokens in config →
hasSole()false → iterates all → hits expired token-A → unhandledRequestException→ crash.After the fix:
FIXED: Config always has only the latest tokens. Even if stale tokens somehow remain, the
try/catchskips them gracefully.Fix
Auth.phpuses newsetApiTokens()to atomically replace all tokens on re-authHasAClient.phpwraps each token's API call intry/catch RequestException— expired tokens are skipped and cleaned upConfigRepository.phpaddssetApiTokens(Collection $tokens)for bulk replacementRegression tests
6 new tests in
tests/Unit/ConfigRepositoryTest.phpthat fail on main and pass on this branch:Full suite: 37 passed, 0 failed (45 assertions).
Test plan
cloud authmultiple times → verifyconfig.jsondoes not accumulate duplicate tokenscloud environment:variables→ verify each organization appears only once in the pickerconfig.json→ verify they are deduplicated on readcloud authtwice → verifyconfig.jsononly contains tokens from the latest session./vendor/bin/pest→ 37 passed, 0 failed🤖 Generated with Claude Code