Skip to content

fix: throttle and bound the settings API connection check#577

Draft
galbus wants to merge 1 commit into
mainfrom
claude/intelligent-hypatia-00e4f1
Draft

fix: throttle and bound the settings API connection check#577
galbus wants to merge 1 commit into
mainfrom
claude/intelligent-hypatia-00e4f1

Conversation

@galbus

@galbus galbus commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

The BeyondWords settings page revalidated the REST API connection on every page load and could hide unrelated settings tabs during any API blip. This fixes both the performance and availability problems.

Settings::maybe_validate_api_creds() fires on the load-settings_page_beyondwords hook whenever the active tab is Authentication — which is the default tab used by both the admin-menu link and the plugin-row Settings link (neither passes ?tab=). It called Utils::validate_api_connection(), which:

  • delete_option()'d the beyondwords_valid_api_connection flag before issuing an uncached GET /projects/{id} with a 30-second timeout, and never short-circuited — despite the docblock claiming it did; and
  • because the flag was cleared first, any transient failure (timeout, DNS error, 5xx, WP_Error) left it unset, so Tabs::get_visible_tabs() collapsed the UI to the Authentication tab only.

Impact

  • Performance (VIP): an uncached blocking remote request with a 30s timeout on the admin render path, repeated on every view — ties up a PHP worker per load and hangs the settings page during API slowness.
  • Availability: during any API outage the Integration and Preferences tabs disappeared, locking the operator out of unrelated settings until a later validation happened to succeed.

What changed

src/settings/class-utils.phpvalidate_api_connection()

  • Short-circuit / throttle: a beyondwords_api_connection_checked transient (5-min TTL) keyed to an MD5 fingerprint of the current credentials. Repeat page loads skip the network call entirely; changing the API key or project ID busts the fingerprint, so saving new credentials revalidates immediately instead of waiting out the window.
  • Flag preserved on transient failure: the pre-emptive delete_option() is gone. The flag is now cleared only on a definitive auth failure — 401 (already handled in Client::call_api()) or 403 (mirrored here). Timeouts / 5xx / WP_Error leave the last known-good flag intact, so an API blip can no longer hide the other tabs.
  • Bounded timeout: the validation GET now uses a 3-second timeout (VIP's ceiling) instead of 30s.
  • Removed the dead delete_transient('beyondwords_validate_api_connection') line — that transient was never set anywhere (grep-confirmed).
  • Missing credentials clear the flag (preserving prior behaviour) without any API call.

src/api/class-client.phpcall_api() / build_args()

  • Added an optional $timeout argument (default DEFAULT_TIMEOUT = 30, so all existing callers are unchanged). The validation GET passes 3.
  • Because the timeout is now a variable rather than a literal > 3, the VIP WordPressVIPMinimum.Performance.RemoteRequestTimeout sniff no longer fires — so the previous // phpcs:ignore on that line was removed. No new ignores were added (per the VIP non-negotiable in AGENTS.md).
  • call_api()'s 401-clears-the-flag behaviour is unchanged; 403 handling is scoped to the validation path only, so audio-generation calls are unaffected.

Tests (tests/phpunit/settings/)

  • Throttle-transient cleanup added to setUp/tearDown in test-utils.php and test-settings.php.
  • Seven new validate_api_connection cases: flag preserved on 500, flag preserved on WP_Error, flag cleared on 403, flag cleared when credentials removed, throttle skips the repeat call, and re-validate on credentials change.

Behaviour matrix

Response Flag before Flag after Tabs
200 any set (timestamp) all three
401 / 403 set cleared Authentication only
timeout / DNS / 5xx / WP_Error set unchanged all three (blip absorbed)
within 5-min window (same creds) any unchanged (no API call) as-is

Testing / reviewer notes

  • PHPCS: ✅ clean. Confirmed both via a direct vendor/bin/phpcs run against the repo's .phpcs.xml and via the repo's grumphp pre-commit hook (phpcs task passed), including verifying that removing the phpcs:ignore does not reintroduce a timeout violation.
  • php -l: clean on all four files.
  • PHPUnit: the seven new/updated tests follow the existing pre_http_request-stub patterns exactly. I was unable to execute them locally because the worktree's wp-env test container could not start — the shared Docker daemon was saturated by ~20 parallel wp-env stacks (all base images were cached; the daemon itself was unresponsive). CI will run the full PHPUnit suite — please confirm it's green before merging.

Draft until CI validates the PHPUnit run.

🤖 Generated with Claude Code

Utils::validate_api_connection() ran on every settings-page load of
the (default) Authentication tab. It deleted the
beyondwords_valid_api_connection flag *before* an uncached,
30-second-timeout API call and never short-circuited, so:

- a slow API blocked the admin page render for up to 30s on every
  view — a VIP performance violation (uncached blocking remote
  request on a render hot path);
- any transient failure (timeout, DNS error, 5xx, WP_Error) left the
  flag deleted, collapsing Tabs::get_visible_tabs() to the
  Authentication tab only and locking operators out of the
  Integration and Preferences tabs during any API blip.

Fix:

- Short-circuit with a throttle transient
  (beyondwords_api_connection_checked, 5-minute TTL) keyed to a
  fingerprint of the current credentials. Repeat loads skip the
  network call; changing the API key or project ID busts the
  fingerprint so a credentials save re-validates immediately.
- Stop deleting the flag pre-emptively. It is now cleared only on a
  definitive auth failure: 401 (already handled in
  Client::call_api) or 403 (mirrored in validate_api_connection).
  Transient failures preserve the last known-good flag.
- Use a bounded 3-second timeout for the validation GET.
  call_api()/build_args() gained an optional $timeout (default 30);
  because the timeout is now a variable rather than a literal > 3,
  the VIP RemoteRequestTimeout sniff no longer fires, so the
  previous phpcs:ignore was removed (no new ignores added).
- Remove the dead
  delete_transient('beyondwords_validate_api_connection') call —
  that transient was never set anywhere in the codebase.

Adds PHPUnit coverage: flag preserved on 500 and on WP_Error, flag
cleared on 403 and when credentials are removed, throttle skips the
repeat call, and a credentials change forces re-validation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

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