Fix OAuth client credentials and gzip decoding#27
Closed
petersentaylor wants to merge 1 commit intosteipete:mainfrom
Closed
Fix OAuth client credentials and gzip decoding#27petersentaylor wants to merge 1 commit intosteipete:mainfrom
petersentaylor wants to merge 1 commit intosteipete:mainfrom
Conversation
omarshahine
added a commit
to omarshahine/eightctl
that referenced
this pull request
Apr 15, 2026
…dless envs - Replace infinite 429/401 retry loops with bounded retries (max 3) and exponential backoff to prevent permanent rate-limit storms - Remove explicit Accept-Encoding: gzip header; let Go's http.Transport handle compression transparently (simpler, no manual gzip.NewReader) - Detect headless environments (SSH, no TTY, EIGHTCTL_KEYRING_FILE=1) and fall back to file-based keyring to avoid macOS Keychain hang Credit to @davidfencik (steipete#16) for the retry and keyring patterns, and @petersentaylor (steipete#27) for the simpler gzip approach. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Collaborator
|
Clean and elegant approach, @petersentaylor — just removing the explicit Closing this in favor of #24, which combines your gzip approach with OAuth form-encoding, bounded retries, and headless keyring support. Thanks for the contribution — we'll get this released soon! 🙏 |
omarshahine
added a commit
that referenced
this pull request
Apr 16, 2026
* fix(client): use form-urlencoded for OAuth token endpoint The Eight Sleep auth server (auth-api.8slp.net/v1/tokens) expects standard OAuth2 form-urlencoded requests, not JSON. The previous implementation sent JSON with hardcoded "sleep-client" credentials, which caused a 400 from Joi validation. The fallback to legacy /login then tripped the rate limiter, resulting in a permanent 429 loop. Changes: - Send application/x-www-form-urlencoded instead of application/json - Use c.ClientID and c.ClientSecret (the real app creds extracted from the Android APK) instead of hardcoded "sleep-client"/"" - Make authURL a var so tests can point it at a local server - Add tests for form encoding, credential passthrough, and legacy login fallback Fixes #7, fixes #8, fixes #12 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(client): decompress gzip API responses The do() method sends Accept-Encoding: gzip but never decompresses the response body, causing json.Decode to fail with: invalid character '\x1f' looking for beginning of value (0x1f is the gzip magic byte.) Check Content-Encoding: gzip on responses and wrap the body in a gzip.Reader before decoding. Added test with a mock gzip response. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: cap retry loops, simplify gzip handling, fix keyring hang in headless envs - Replace infinite 429/401 retry loops with bounded retries (max 3) and exponential backoff to prevent permanent rate-limit storms - Remove explicit Accept-Encoding: gzip header; let Go's http.Transport handle compression transparently (simpler, no manual gzip.NewReader) - Detect headless environments (SSH, no TTY, EIGHTCTL_KEYRING_FILE=1) and fall back to file-based keyring to avoid macOS Keychain hang Credit to @davidfencik (#16) for the retry and keyring patterns, and @petersentaylor (#27) for the simpler gzip approach. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(client): drop legacy /login fallback, OAuth-only auth The legacy /login endpoint no longer works reliably upstream, and the silent fallback was masking real OAuth errors. Remove it so OAuth failures surface directly. Also revert the keyring-backend tweaks from 9f49cf0 — leave tokencache behavior identical to main. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): remove contradictory golangci-lint config `disable-all: true` and `disable: [errcheck, unused]` can't be combined — golangci-lint errors with "can't combine options --disable-all and --disable". Remove the redundant `disable` list and stale `revive` settings since only `govet` is enabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Lobster <lobster@shahine.com> 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
Accept-Encoding: gzipso Go can transparently decode API responsesWhy
The current auth flow sends a hardcoded
sleep-clientpayload with an empty client secret, which causes the OAuth token endpoint to fail and forces the CLI onto the legacy login fallback. In practice that fallback can get rate-limited with429 Too Many Requests.After fixing the OAuth request, live status requests succeeded, but response decoding still failed because the client explicitly requested gzip and then attempted to JSON-decode compressed bytes directly. Removing the manual
Accept-Encodingheader lets Go handle decompression correctly.Verification
go test ./...eightctl statuson macOS after rebuilding the binary