# How the Readiness Probe Determines When the Dev Server Is Ready in SXO

> Discover how the SXO readiness probe uses waitForHttp to poll the dev server's endpoint and determine readiness by checking status codes 200-499. Learn about backoff and jitter.

- Repository: [Víctor García/sxo](https://github.com/gc-victor/sxo)
- Tags: internals
- Published: 2026-03-02

---

**The readiness probe in SXO uses the `waitForHttp` function in [`src/js/cli/open.js`](https://github.com/gc-victor/sxo/blob/main/src/js/cli/open.js) to poll the development server's HTTP endpoint, treating any response with status codes 200–499 as "ready" while retrying with exponential backoff and jitter until the configured timeout expires.**

The `gc-victor/sxo` repository implements a sophisticated readiness probe to determine exactly when the development server is ready to accept traffic. This mechanism ensures that browsers open only after the server is actually serving requests, preventing connection errors during the startup phase. The probe logic resides primarily in [`src/js/cli/open.js`](https://github.com/gc-victor/sxo/blob/main/src/js/cli/open.js) and combines intelligent HTTP polling with configurable retry strategies.

## The Core Mechanism: `waitForHttp` and `openWhenReady`

The readiness determination is orchestrated by two primary functions exported from [`src/js/cli/open.js`](https://github.com/gc-victor/sxo/blob/main/src/js/cli/open.js): **`openWhenReady`** and **`waitForHttp`**. The `openWhenReady` function serves as the high-level entry point that builds the target URL and initiates the probe, while `waitForHttp` implements the low-level polling logic.

### Building the Target URL

Before probing begins, `openWhenReady` constructs the target URL using an internal `buildUrl` helper. As seen in lines 54–55 of [`src/js/cli/open.js`](https://github.com/gc-victor/sxo/blob/main/src/js/cli/open.js), this combines the protocol, host, port, and pathname to form the complete endpoint that will be polled.

### Abort Signal Handling

The probe respects external cancellation through the **`AbortSignal`** API. If an already-aborted signal is passed to `openWhenReady`, the function returns immediately without attempting any HTTP requests (lines 57–59). Throughout the polling loop, `waitForHttp` checks the signal state and terminates early if an abort is requested (lines 30–33).

## How the Probe Determines "Ready" Status

The readiness probe employs a permissive status code check combined with intelligent request method fallback to determine when the server is accepting traffic.

### Status Code Acceptance (200–499)

Inside `waitForHttp`, any HTTP response with a status code **greater than or equal to 200 and less than 500** is treated as "ready" (lines 41–42). This includes successful responses (2xx), redirects (3xx), and client errors such as **404 Not Found**. Notably, this means the probe considers the server ready even if the specific path returns a 404, as long as the server is responding. Only server-error responses (5xx) trigger the retry mechanism.

### HEAD First, GET Fallback

By default (`headFirst: true`), the probe sends a **`HEAD`** request first to minimize bandwidth (lines 48–55). If the server responds with a ready status, the probe succeeds immediately. If the server returns **405 Method Not Allowed** or **501 Not Implemented**, the probe automatically falls back to a **`GET`** request (lines 56–59). This fallback logic ensures compatibility with servers that do not implement HEAD while maintaining efficiency for those that do.

## Retry Logic and Timing

The readiness probe implements a sophisticated retry mechanism with exponential backoff, jitter, and adaptive per-request timeouts to balance responsiveness with system load.

### Exponential Backoff with Jitter

When a request fails or returns a 5xx status, `waitForHttp` waits before retrying. The delay follows an exponential backoff pattern with a factor of approximately **1.7**, capped at `maxDelayMs` (lines 25–28). To prevent thundering herd problems, the implementation adds a jitter of **±10%** to each delay (lines 68–71). This randomized backoff ensures that multiple concurrent probes do not synchronize their retry attempts.

### Per-Attempt Timeouts

Each individual HTTP request uses an adaptive timeout calculated as `perAttempt` (lines 44–46). This value ranges between **300 ms and 2 seconds**, scaled based on the remaining overall timeout budget. By limiting the duration of any single request, the probe ensures that a slow or hanging connection does not exhaust the entire `timeoutMs` budget, allowing more retry attempts within the allocated time.

## Implementation Examples

The following examples demonstrate how to use the readiness probe in practice.

### Basic Usage with `openWhenReady`

```javascript
// Open the browser when a dev server on port 3000 is ready
import { openWhenReady } from "./src/js/cli/open.js";

const result = await openWhenReady({
  port: 3000,
  pathname: "/",          // optional, defaults to "/"
  timeoutMs: 15000,      // wait up to 15 seconds
  verbose: true,         // enable debug logging of retries
});

console.log(result);
// → { opened: true, url: "http://localhost:3000/", timedOut: undefined }

```

### Low-Level Probe with `waitForHttp`

```javascript
// Use the low-level probe directly
import { waitForHttp } from "./src/js/cli/open.js";

const ready = await waitForHttp("http://localhost:3000", {
  timeoutMs: 8000,
  headFirst: true,      // default – try HEAD first
  verbose: false,
});

if (ready) {
  console.log("Dev server is up!");
} else {
  console.warn("Server did not become ready in time.");
}

```

### Aborting the Probe

```javascript
// Abort the probe after an external signal
import { openWhenReady } from "./src/js/cli/open.js";

const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // abort after 5s

const result = await openWhenReady({
  port: 3000,
  signal: controller.signal,
});

console.log(result);
// → { opened: false, url: "http://localhost:3000/", timedOut: false, error: "aborted" }

```

## Summary

The readiness probe in SXO determines when the development server is ready through a sophisticated HTTP polling mechanism:

- **Permissive status checking**: Accepts HTTP 200–499 (including 404s) as "ready", retrying only on 5xx errors
- **Method fallback**: Attempts HEAD requests first, falling back to GET if the server returns 405 or 501
- **Intelligent retry logic**: Implements exponential backoff with 1.7x factor, ±10% jitter, and adaptive per-request timeouts (300ms–2s)
- **Abort support**: Respects AbortSignal for graceful cancellation
- **Entry points**: `openWhenReady` for high-level browser opening, `waitForHttp` for low-level probing

## Frequently Asked Questions

### What HTTP status codes does the SXO readiness probe accept as "ready"?

The probe treats any response with a status code **greater than or equal to 200 and less than 500** as indicating the server is ready. This includes successful responses (2xx), redirects (3xx), and client errors such as **404 Not Found**. Only server-error responses (5xx) trigger the retry mechanism, as implemented in lines 41–42 of [`src/js/cli/open.js`](https://github.com/gc-victor/sxo/blob/main/src/js/cli/open.js).

### How does the probe handle servers that don't support HEAD requests?

By default, the probe sends a **HEAD** request first to minimize bandwidth. If the server responds with **405 Method Not Allowed** or **501 Not Implemented**, the probe automatically falls back to a **GET** request. This fallback logic ensures compatibility with servers that do not implement HEAD while maintaining efficiency for those that do, as seen in lines 48–59 of [`src/js/cli/open.js`](https://github.com/gc-victor/sxo/blob/main/src/js/cli/open.js).

### Can I configure the timeout and retry behavior?

Yes, the probe accepts several configuration options. You can set **`timeoutMs`** to control the total waiting time, and the internal retry logic uses an exponential backoff factor of approximately **1.7** with **±10% jitter** and a cap on maximum delay. Additionally, each individual request uses an adaptive timeout between **300 ms and 2 seconds** based on remaining time budget, ensuring efficient resource usage.

### What happens if the readiness probe is aborted mid-check?

The probe respects the **`AbortSignal`** API for graceful cancellation. If an abort signal is triggered—either before the probe starts (lines 57–59) or during the retry loop (lines 30–33)—the `waitForHttp` function immediately stops polling and returns `false`. The `openWhenReady` wrapper then returns an object indicating the failure reason, including `{ opened: false, error: "aborted" }`, allowing calling code to handle cancellation appropriately.