Long-Running Requests

What It Is

Most requests to Jetstack answer within a second or two. Some take longer: an action run on hundreds of selected records, an automation that calls a slow outside system for each record, an import of a large file, an MCP tool that lists thousands of rows.

Between the browser and Jetstack sit proxies — Cloudflare, a load balancer, the web server — and each of them gives up on a request that stays silent for too long. Cloudflare answers 524 when nothing has arrived for about 100 seconds, although the work goes on and usually completes on the server.

Jetstack keeps such a request open. Once it has run for 15 seconds, its answer starts: a block of blank padding goes out at once and another every 15 seconds while the work continues. The result follows the padding when the work is done, and the browser reads it as usual.

Why It Matters

  • Bulk work finishes visibly. A person who runs an action on 500 records sees the result instead of a timeout page, and is not tempted to run it again.
  • Nothing to configure. Every screen of the application is covered: lists and their actions, forms, imports.
  • Integrations can opt in. The MCP server streams on its own; REST clients ask for it with one header.

How It Works

  1. A request runs as usual. If it finishes within 15 seconds, nothing changes.
  2. At 15 seconds it commits: the status (always 200) and the headers go out with the first padding.
  3. Every 15 seconds, while database queries or calls to other systems are running, more padding follows.
  4. The result comes last: the JSON answer of a screen, the page of a submitted form, or the MCP message.

Once the answer has started, a few things work differently, because the status and headers have already been sent:

What the request does at the endWhat the person or client gets
Shows the result (snippets, flash messages)As usual
RedirectsThe redirect is written into the answer and followed by the browser
Downloads a fileA link to a temporary copy, opened like any download from an action
Fails with an errorThe usual error dialog ("Error When Processing Request")
Sets a status code (REST, MCP)Status 200; the answer's body says what happened

Where It Applies

  • Screens — every AJAX request of the application and every submitted form. Sign-in, file uploads and downloads, webhooks and custom app handlers answer with status codes their callers rely on, so they never stream.
  • MCP — a call whose client accepts an event stream (MCP clients do) is answered as one: comment lines keep the connection open and the result is the stream's message. See MCP.
  • REST API — only when the client sends X-Jetstack-Keep-Alive: 1. A streaming answer's status is 200 whatever happens, which a client that reads status codes must not get unasked. See REST API Reference.

What It Cannot Keep Open

Padding can only go out between pieces of work: between database queries, while waiting for another system, between two records of a bulk action. A single step that runs for more than about 100 seconds without any of these — one PHP script step doing a long computation, for example — still times out. Split such work into smaller steps, or move it out of the request:

  • run it on a schedule (Scheduler),
  • start it and come back for the result with an asynchronous wait-point (Async Wait-Points And Live Results),
  • for exports of a whole list, use the background export, which already runs as a job.

Example

A view action "Send reminders" runs an automation that calls an accounting system once per invoice; each call takes about a second. Run on 300 selected invoices, the request takes five minutes.

  • 0–15 s: the browser shows its progress bar as usual.
  • 15 s: the answer starts; every 15 seconds another block of padding arrives, so no proxy closes the connection.
  • 5 min: the automation has run for every invoice; the list reloads and the flash messages appear.

Without the padding, Cloudflare would have shown a 524 page after 100 seconds while the reminders kept going out — and a second click would have sent them twice.

Best Practices

  • Keep long work in small steps: a bulk action over many records streams well, one giant script step does not.
  • Treat anything over a few minutes as a job: schedule it or make it asynchronous, and let people come back for the result.
  • In REST integrations, send X-Jetstack-Keep-Alive: 1 only on calls you know can be long, and read the outcome from the body.

Settings

The operator sets these environment variables; tenants do not change them.

JETSTACK_KEEPALIVE_AFTER

Seconds a request runs before its answer starts streaming. Default 15. Lower values commit more requests, with nothing to gain below the proxies' limits.

JETSTACK_KEEPALIVE_EVERY

Seconds between two blocks of padding of a streaming answer. Default 15. Keep it well below the shortest idle limit in front of Jetstack (Cloudflare: 100 seconds).