Skip to content

Comments

fix: gzip decompression, infinite retry loops, and keychain hangs#16

Open
davidfencik wants to merge 6 commits intosteipete:mainfrom
davidfencik:fix/auth-gzip-keyring
Open

fix: gzip decompression, infinite retry loops, and keychain hangs#16
davidfencik wants to merge 6 commits intosteipete:mainfrom
davidfencik:fix/auth-gzip-keyring

Conversation

@davidfencik
Copy link

@davidfencik davidfencik commented Feb 19, 2026

Summary

Three fixes that resolve CLI hangs and endpoint failures, especially on macOS in headless environments.

Fixes: #10, #14 (and the root cause behind most symptoms in #12)

Changes

1. Remove explicit Accept-Encoding: gzip headers

Go's http.Transport handles gzip transparently when Accept-Encoding is not set manually. Setting it explicitly disables automatic decompression, causing raw gzip bytes to reach json.Decoderinvalid character '\\x1f' errors on most endpoints.

2. Add max retries for 429/401 responses

do() recursed infinitely on 429 (rate limit) and 401 (unauthorized). Added a retry cap (3) with linear backoff to prevent the CLI from hanging forever.

3. Skip macOS Keychain in headless environments

The macOS Keychain backend blocks indefinitely in headless environments (SSH, cron, launchd) waiting for an auth prompt that can never be shown. Now detects headless mode (SSH_TTY set, TERM missing) and falls back to file backend. Interactive users keep Keychain as default. Can also be forced with EIGHTCTL_KEYRING_FILE=1.

Testing

  • All existing tests pass (go test ./...)
  • Verified status, sleep day, whoami, device info all work on macOS (Apple Silicon, headless via launchd)
  • Interactive Keychain behavior preserved for terminal users

Context

I was setting up eightctl to run as part of a home automation system (via launchd on macOS). Hit all three issues in sequence: gzip errors, infinite retry loops, and keychain hangs. Debugged with the help of an AI coding assistant (Claude/OpenClaw) by reading the source, tracing the issues, and building locally.

Each fix is a separate commit for easy review.

Go's http.Transport handles gzip decompression transparently when
Accept-Encoding is not set manually. Setting it explicitly disables
automatic decompression, causing raw gzip bytes to reach json.Decoder
and producing 'invalid character' errors on most endpoints.

Fixes steipete#14
The do() method recursed indefinitely on 429 (rate limit) and 401
(unauthorized) responses. This could hang the CLI forever when the
API consistently returns these status codes.

Add a retry counter (max 3) with exponential backoff for 429s.
The macOS Keychain backend blocks indefinitely in headless environments
(SSH, cron, launchd) when it cannot show the authorization prompt.

Detect headless mode via SSH_TTY or missing TERM and fall back to
file-only backend. Interactive terminal users keep Keychain as default.
Can also be forced with EIGHTCTL_KEYRING_FILE=1.

Fixes steipete#10
@davidfencik davidfencik force-pushed the fix/auth-gzip-keyring branch from 563dcf4 to 92e8e81 Compare February 19, 2026 15:05
Gerry Fencik added 3 commits February 20, 2026 12:32
Corrects field mappings for sleep data parsing:
- sleepDuration (not sleepDurationSeconds)
- Adds deepDuration, remDuration, lightDuration, sleepStart/End
- Properly nests sleepQualityScore with hrv, heartRate, respiratoryRate
- Fixes sleep range --from/--to flag parsing (read from cobra, not viper)
- Displays durations in hours for readability

Inspired by talison's analysis in steipete#11.
…uality)

Adds to sleep day/range output:
- quality (sleepQualityScore.total)
- awake_min (presenceDuration - sleepDuration)
- disturbances (renamed from tnt)
- avg_rhr (rolling average resting heart rate)
- lowest_hr (min from session timeseries heartRate data)
- breath_rate (respiratoryRate.current)
- snore_min (snoreDuration in minutes)

Parses sessions[].timeseries.heartRate array to extract lowest HR.
Also adds presenceDuration, snoreDuration, heavySnoreDuration to SleepDay struct.
- Add --side flag to sleep day/range: left, right, partner, or me
- Fix --date flag reading from cobra instead of viper (same fix as --from/--to)
- Fix OAuth token endpoint using hardcoded 'sleep-client' instead of actual client ID
- Add GetSleepDayForUser() to query any user's trends
- Add Device().SideUserIDs() and UserIDForSide() helpers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant