Skip to content

fix: cache Cloud profiles and make USB connections opt-in - #668

Open
jeroenwienk wants to merge 9 commits into
developfrom
codex/cache-cloud-profile-rate-limits
Open

jeroenwienk wants to merge 9 commits into
developfrom
codex/cache-cloud-profile-rate-limits

Conversation

@jeroenwienk

@jeroenwienk jeroenwienk commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Repeated CLI commands could hit Athom Cloud profile rate limits before reaching Homey, and USB discovery added a one-second timeout for unrelated 10.* interfaces. Cache the account profile across invocations and make USB connections explicitly opt-in.

Cloud profile caching

Cache the account profile and Homey connection details for five minutes. On HTTP 429, reuse the last successful profile and persist a one-minute refresh cooldown. homey list --refresh and homey whoami --refresh refresh account data while respecting that cooldown.

Bind cache entries to a hash of the OAuth access token or PAT captured before the request, clear caches on login/logout, and serialize updates with a process-shared lock. Cache I/O failures warn without discarding fetched profiles or usable rate-limit fallbacks. Actual Homey responses remain live.

Explicit USB connections

Existing USB users must now pass --usb or set HOMEY_USB=1. Explicit --no-usb overrides the environment setting. Supported commands are list, select, app run, app install, generated API operations, api raw and its aliases, and api diagnose.

  • Normal mode performs no USB probes and retains existing LAN/Cloud transport behavior.
  • USB mode requires a detected local API-v3 Homey belonging to the authenticated account. It configures the USB address before SDK login, takes precedence over --discovery-strategies, disables alternative discovery strategies, and never falls back to LAN or Cloud transport.
  • USB listing and selection show only detected Homeys, including those with cached offline Cloud status. Empty lists succeed; selection and targeted commands fail clearly when the device is unavailable.
  • Selection saves only the Homey identity. Lists and authenticated clients are separated by connection mode in memory; refresh invalidates both lists. USB addresses never enter the persistent profile cache.
  • Token mode supports --token <TOKEN> --homey-id <ID> --usb. Enabled USB mode rejects --address.
  • api diagnose --usb reports only the usb strategy using the existing report structure. Account authentication and session renewal can still require Athom Cloud.

Merge Robin's fix/parallel-usb-probe branch with its history, intervening commits, and 4.4.5 bump. Retain concurrent probes of unique addresses with the existing one-second timeout, and adapt the regression test to getProfile(). This branch integration leaves PR #674 open; it does not merge it into develop.

Also integrate current develop to resolve its connection-strategy conflicts, retaining its 4.5.0 version, Homey API update, and Python find-links fix. Explicit network discovery strategies remain available in normal mode; enabled USB mode remains strict.

Validation

The merge baseline passes all 269 tests. Feature tests cover flag/environment precedence, zero default probes, concurrency and deduplication, USB-only selection, offline and unsupported targets, mode isolation, authentication and request routing, token mode, API aliases, diagnostics, and app execution/cleanup. All 311 tests pass with coverage (51.78% lines, 75.75% branches, 68.26% functions), above repository thresholds. ESLint, repository-wide Prettier, and diff checks pass. Network behavior is tested with isolated fixtures and mocked endpoints; physical USB hardware has not been tested.

Asana: https://app.asana.com/1/739269396989735/task/1218190357212920

@jeroenwienk jeroenwienk self-assigned this Sep 7, 2026
jeroenwienk and others added 2 commits September 7, 2026 17:43
Local discovery probed one candidate address per network interface
sequentially, each with a one second timeout, so every 10.x interface that
is not a Homey added a full second to getHomeys. Virtualization adapters
commonly add several of those. Interfaces sharing a subnet also produced a
duplicate probe for the same address.

Collect the unique candidate addresses first, then probe them concurrently.
With four such interfaces, two of them in one subnet, this drops from
4006ms to 1004ms; with eight it drops from 9012ms to 1005ms. The total is
now bounded by the timeout instead of scaling with the interface count.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cross-process cache races can discard cooldowns, restore stale profiles, and cross OAuth account boundaries.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds persistent account-profile caching to keep CLI commands working during Cloud API rate limits.

Changes:

  • Adds five-minute profile caching with 429 fallback and cooldown.
  • Adds --refresh to list and whoami.
  • Adds documentation and cache-focused tests.
File summaries
File Description
lib/AthomApi.js Integrates profile caching and rate-limit handling.
lib/AthomApiProfileCache.js Implements persistent atomic cache writes.
bin/cmds/list.mjs Adds account-data refresh support.
bin/cmds/whoami.mjs Adds account-data refresh support.
README.md Documents caching and rate-limit behavior.
tests/lib/athom-api.cache.test.mjs Tests cache behavior and isolation.
tests/lib/athom-api.fetch.test.mjs Updates local-discovery mocks.
Review details

Suppressed comments (1)

lib/AthomApi.js:211

  • This is not a safe cross-process update because stored was captured before the network request. If another invocation successfully writes a newer profile while this one receives 429, this write restores stale data; in the reverse completion order, the success write removes the newly recorded cooldown. Serialize or atomically merge cache updates so both the newest profile and an active retryAfter survive overlapping refreshes.
      await this._profileCache.set({
        ...stored,
        retryAfter: Date.now() + PROFILE_RATE_LIMIT_COOLDOWN,
      });
  • Files reviewed: 5/7 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/AthomApi.js Outdated
Comment thread lib/AthomApiProfileCache.js
@jeroenwienk jeroenwienk changed the title fix: keep CLI commands running during cloud profile rate limits fix: cache Cloud profiles and make USB connections opt-in Sep 14, 2026
@jeroenwienk
jeroenwienk marked this pull request as ready for review September 15, 2026 12:08
@jeroenwienk
jeroenwienk requested a balanced review from Copilot September 15, 2026 12:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Logout races can repopulate the profile cache, and disconnected USB diagnostics bypass the report structure.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 20/28 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment on lines +29 to +30
if (stored?.authKey !== value.authKey) {
return value;
Comment thread lib/api/ApiCommandRuntime.mjs Outdated
const preferredStrategyIds = getPreferredAuthenticateStrategy(homey);
const attemptedStrategyIds = getDiagnoseStrategyOrder(homey);
export async function diagnoseHomeyStrategies({ homeyId, usb = false } = {}) {
const homey = await resolveRequestedHomey(homeyId, { usb });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants