Skip to content

Allow longer time-to-first-byte for AI API requests (cURL low-speed timeout) - #3120

Merged
fredrikekelund merged 2 commits into
trunkfrom
fix/http-low-speed-timeout-for-ai-requests
Jul 7, 2026
Merged

Allow longer time-to-first-byte for AI API requests (cURL low-speed timeout)#3120
fredrikekelund merged 2 commits into
trunkfrom
fix/http-low-speed-timeout-for-ai-requests

Conversation

@chubes4

@chubes4 chubes4 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Related issues

Fixes #3118.

Proposed Changes

  • Raise CURLOPT_LOW_SPEED_TIME from 30 seconds to 120 seconds in getStandardMuPlugins' 0-http-request-timeout.php.
  • Update the inline documentation to explain the new threshold and the AI-API-request use case.
  • No other thresholds changed: CURLOPT_CONNECTTIMEOUT stays at 30s, CURLOPT_LOW_SPEED_LIMIT stays at 1024 B/s, http_request_timeout stays at 300s.

Background

The 0-http-request-timeout.php mu-plugin was introduced in #1407 (May 2025, hard 60s timeout) and refined in #1913 (October 2025, replaced hard timeout with progress-based watchdog). Both PRs were correct for their motivation: plugin/theme downloads on slow connections. The #1913 design — abort if transfer stays below 1 KB/s for 30 consecutive seconds — fails fast on dead mirrors while letting working slow downloads (Jetpack 33 MB on 3G) complete.

Since October 2025, AI-powered WordPress plugins (Data Machine, Intelligence, and others) have become a common Studio use case. Non-streaming wp_remote_post calls to LLM providers produce zero bytes on the socket until the model emits its first token. On flagship models with long prompts, that first token commonly takes 30 to 90 seconds. The watchdog fires before any content is delivered, aborting with cURL error 28.

See #3118 for the full reproduction and context.

Side effects of the change

Use case Before (30s) After (120s)
Plugin/theme download on fast internet works works
Plugin/theme download on slow internet (Jetpack-on-3G test from #1913) works (bytes flow >1 KB/s) works (same)
Dead mirror (0 B/s forever) aborts in 30s aborts in 120s
Hung connection (bytes delivered, then stall) aborts 30s after stall aborts 120s after stall
AI API request with <30s TTFB works works
AI API request with 30-120s TTFB aborts with cURL 28 works
AI API request with >120s TTFB aborts aborts via 300s http_request_timeout outer cap

The only observable behavior change: AI API requests that were dying now complete. The only regression is that a truly hung connection takes 90 seconds longer to detect. The 300-second outer cap still catches fully broken requests. This is a small DX trade-off for an uncommon failure mode, in exchange for first-class AI API support.

No new escape-hatch filter, no URL-based discrimination, no new configuration knob. The one default value moves to a number that makes both use cases work.

Testing Instructions

Existing #1913 regression test (plugin download on throttled connection)

Per Bernardo's original methodology:

  1. Install Network Link Conditioner (from Apple's Additional Tools for Xcode)
  2. Set throttling to a slow profile (e.g., "3G" or "Edge")
  3. In Studio, navigate to /wp-admin/plugin-install.php
  4. Install the Jetpack plugin (33 MB)
  5. Expected: completes successfully on throttled connection (may take several minutes)

Should pass identically before and after this change — the slow-but-working download produces far more than 1 KB/s throughout, so the low-speed watchdog never triggers regardless of whether the threshold is 30s or 120s.

New test case (slow-TTFB AI API request)

  1. Install a plugin that uses wp_remote_post to call https://api.openai.com/v1/chat/completions (e.g., Data Machine or any ai-http-client consumer).
  2. Configure it to use a flagship OpenAI model (gpt-5.4 or similar).
  3. Send a non-streaming request with a large prompt (~20 KB system prompt + long user message designed to trigger reasoning).
  4. Expected before: fails with cURL error 28 ("Operation too slow. Less than 1024 bytes/sec transferred during the last 30 seconds") on requests where OpenAI's TTFB exceeds 30 seconds.
  5. Expected after: completes normally for any TTFB up to 120 seconds; fails via the 300s outer cap beyond that.

Dead-connection regression test

  1. wp eval 'var_dump(wp_remote_get(\"https://example.invalid/never-responds\"));'
  2. Expected: times out in approximately 30s (connect phase — not affected by this change).

For a case that reaches the low-speed watchdog specifically, you'd need a server that accepts the connection but then sends zero bytes indefinitely (uncommon). That case would now take 120s to detect instead of 30s.

Verifying the change is live in WASM

// Run via wp eval on a Studio site after restarting it:
global $wp_filter;
foreach ( $wp_filter['http_api_curl']->callbacks as $prio => $cbs ) {
    foreach ( $cbs as $cb ) {
        if ( $cb['function'] instanceof Closure ) {
            $r = new ReflectionFunction( $cb['function'] );
            $f = $r->getFileName();
            $lines = file( $f );
            echo implode( '', array_slice( $lines, $r->getStartLine() - 1, $r->getEndLine() - $r->getStartLine() + 1 ) );
        }
    }
}

Expected output after restart should include CURLOPT_LOW_SPEED_TIME, 120.

What this isn't

AI assistance disclosure

Per WordPress's AI Guidelines, disclosing meaningful AI assistance on this contribution:

  • I used Claude Code to research the history of the mu-plugin (tracing through Increase HTTP request timeout to 60 seconds #1407 and Fix plugin download timeouts by using low-speed timeout instead of hard timeout #1913), evaluate the side effects of possible adjustments (raising the time vs. lowering the limit vs. an escape-hatch filter), and draft the commit message and PR body.
  • The engineering decision (raise time threshold to 120s rather than add a filter or discriminate by URL) was mine after reviewing the options with Claude Code.
  • I tested the fix end-to-end on a live Studio site running the Data Machine plugin, including verifying the compiled mu-plugin contains the new value via PHP reflection on the runtime closure, and re-running the daily memory compaction task against flagship gpt-5.4 to confirm completion.
  • The original cURL 28 reproduction that motivated this fix was observed during real Data Machine usage earlier in the same session.

All substantive design decisions and the testing were my own; AI was used for investigation, pattern recognition, and drafting.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
…imeout)

Fixes #3118.

The mu-plugin injected into every Studio site by getStandardMuPlugins
sets CURLOPT_LOW_SPEED_LIMIT=1024 + CURLOPT_LOW_SPEED_TIME=30, aborting
any outbound HTTP request where transfer rate stays below 1 KB/s for
30 consecutive seconds.

These values were correct for the original motivation (plugin/theme
downloads, #1407/#1913) -- a dead mirror delivering zero bytes is a
broken connection, and aborting fast is the right behavior.

They are incorrect for AI API requests, now a common Studio use case
for plugin developers building on top of OpenAI, Anthropic, Google,
etc. Non-streaming wp_remote_post calls to LLM providers produce zero
bytes on the socket until the model emits its first token. On flagship
models with long prompts, that first token commonly takes 30 to 90
seconds to arrive. The watchdog fires before content is delivered and
aborts with cURL error 28 ("Operation too slow").

Raise CURLOPT_LOW_SPEED_TIME from 30 seconds to 120 seconds. All other
thresholds unchanged, including the 300-second http_request_timeout
outer cap. A working plugin download produces far more than 1 KB/s
throughout its lifetime, so the low-speed watchdog never triggers for
that use case -- the #1913 Jetpack-on-3G test passes identically.
Dead-connection detection still works; it takes 120 seconds to detect
instead of 30, which is a small DX regression for an uncommon failure
mode, and the 300-second outer cap still catches fully hung requests.

The only observable behavior change: AI API requests that would have
died at 30 seconds now complete normally (up to the 300-second outer
cap, which is already enough for all current flagship models).

Reproduced cURL 28 aborts from Data Machine's daily memory compaction
against api.openai.com with gpt-5.4 on a ~30 KB prompt. Built the CLI
locally, verified the compiled mu-plugin contains the new value via
PHP reflection on the runtime closure
(CURLOPT_LOW_SPEED_TIME = 120), re-ran the compaction task against
the same flagship model on the same site, and confirmed end-to-end
completion.

AI assistance: I used Claude Code to research the history of this
mu-plugin (tracing through #1407 and #1913), evaluate the side
effects of each possible adjustment (raising the time vs. lowering
the limit vs. an escape-hatch filter), and draft this commit message
and the accompanying PR body. The engineering decision to raise the
time threshold to 120 seconds (rather than add a filter or
discriminate by URL) was mine after reviewing the options. I
verified the fix end-to-end against a live Studio site and the
original reproduction.
@wpmobilebot

wpmobilebot commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 8263bf8 vs trunk

app-size

Metric trunk 8263bf8 Diff Change
App Size (Mac) 1411.96 MB 1411.97 MB +0.00 MB ⚪ 0.0%

site-editor

Metric trunk 8263bf8 Diff Change
load 1027 ms 1045 ms +18 ms ⚪ 0.0%

site-startup

Metric trunk 8263bf8 Diff Change
siteCreation 6507 ms 6600 ms +93 ms 🔴 1.4%
siteStartup 2387 ms 2393 ms +6 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@gcsecsey gcsecsey left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change makes sense to me, thanks for the detailing the motivation for it in the PR description @chubes4!

I tested that the Jetpack installation still completes eventually on a slower connection, and found no regressions:

connection speed plugin install
Image Image
@chubes4

chubes4 commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

Thank you @gcsecsey!

@fredrikekelund fredrikekelund 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.

This looks fine to me 👍 I did some light smoke testing and didn't spot any issues, so I'll go ahead and merge this

@fredrikekelund
fredrikekelund enabled auto-merge (squash) July 7, 2026 14:05
@fredrikekelund
fredrikekelund merged commit 051b942 into trunk Jul 7, 2026
12 checks passed
@fredrikekelund
fredrikekelund deleted the fix/http-low-speed-timeout-for-ai-requests branch July 7, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

4 participants