Timeouts & Limits
The wall-clock timeout every Nexus run runs under (default 3600s, hard ceiling 6h, per-pipeline override), how it is enforced, and the pipeline row-count constraints that bound the blast radius of any single run.
This page is for engineers who need to know exactly how long a run can take, what enforces that, and what caps limit how much a single run can read or change. These are the platform's blast-radius controls: they bound both time and effect so a buggy or runaway pipeline is contained by construction rather than by hope.
The per-run wall-clock timeout
Every run executes under a wall-clock deadline enforced by the worker wrapper — the code that invokes your async def run(ctx). If the run has not reached a terminal state when the deadline elapses, the wrapper abandons the in-flight work and records the run as timeout (a terminal state; see Run Lifecycle).
| Parameter | Value |
|---|---|
| Default timeout | 3600 seconds (1 hour) |
| Hard ceiling | 6 hours — a per-pipeline override cannot exceed this |
| Per-pipeline override | # nexus:timeout <seconds> directive at the top of the module |
| Enforced by | the worker wrapper (host-side), not the pipeline itself |
Setting a per-pipeline timeout
Declare the override inline. A long backfill might ask for more time; a job that should never take more than a minute should say so, so a hang is caught fast rather than tying up an execution lane for the full default hour.
# nexus:timeout 300 # this pipeline must finish within 5 minutes
# nexus:trigger scheduled 0 * * * *
async def run(ctx):
rows = await ctx.adapters.netsuite.read({"collection": "invoices", "limit": 500})
await ctx.workspace.insert("hourly_invoices", rows)
return {"rows": len(rows)}
The override is clamped to the 6-hour hard ceiling. You cannot author a pipeline that runs unbounded — the ceiling is not a suggestion, it is enforced host-side.
Why wall-clock, and what it is not
The timeout is wall-clock, meaning elapsed real time, not CPU time. It is enforced from outside the sandbox by the worker, so a pipeline cannot disable, extend, or catch its own deadline. Be precise about what this is:
- It bounds how long a run occupies an execution lane. That is its job, and it does it reliably.
- It is not a cooperative in-runtime CPU or memory cap. There is no per-run quota that says "this run may use at most N cores or M megabytes." A run that burns CPU or allocates memory within its wall-clock window is bounded only by that window and by the container's own resources. If you need to bound effect rather than time, that is what the row constraints below are for.
Because it is enforced at the wrapper, timeout interacts with cancellation: hitting the deadline is functionally a host-initiated stop. See Cancellation for the mechanics of interrupting an in-flight run and the honest limits on how forcefully a run can be stopped.
Pipeline constraints: bounding effect
Timeouts bound time. Pipeline constraints bound effect — how many rows a single run is permitted to touch. They are a defense-in-depth guardrail against the expensive class of mistakes: an unbounded read that pulls a whole ledger into memory, or a mis-scoped write that updates far more rows than intended.
| Constraint | Bounds |
|---|---|
| Max rows read | Total rows a run may read across adapter operations |
| Max rows created | Total rows a run may insert |
| Max rows updated | Total rows a run may update |
| Max rows deleted | Total rows a run may delete |
When a run exceeds a configured constraint, it fails with a clear, named error rather than silently proceeding — the same way an over-broad update without a where clause is refused. The row counters that back these constraints are the same counters recorded on every run (rows_read, rows_created, rows_updated, rows_deleted), so the limit you set and the number the run reports are the same measurement. See Monitoring & Auditing Pipelines for how those counters surface in the run record.
These caps pair naturally with the write-approval gate (a human signs off before a data-changing run proceeds) and with per-adapter writability gating (views, formula columns, and read-only collections are not writable at all). Together they mean the answer to "how much damage can one run do?" is bounded and knowable up front. See Scheduling Pipelines for the approval gate.
Choosing timeouts and limits
A practical checklist when you put a pipeline into production:
- Set a timeout that matches the job, not the default. A 30-second refresh should not inherit a one-hour deadline; a genuine backfill should ask for the time it needs (up to 6h).
- Set row constraints to a headroom-padded estimate of a healthy run. If a nightly sync normally writes ~2,000 rows, a cap of, say, 20,000 will never bother a healthy run but will stop a runaway that suddenly tries to write two million.
- Fail fast on the read side. Bounding max-rows-read protects memory before a large result is ever materialized.
- Remember the lane. A long timeout ties up one of the default five execution lanes for its whole duration. Tight timeouts keep the tenant responsive.
Limits & guarantees
- Guaranteed: every run has a host-enforced wall-clock deadline it cannot escape; the deadline is capped at 6 hours; row-count constraints, when set, are enforced and cause a clean, named failure when exceeded.
- Not provided: a per-run CPU or memory quota inside the sandbox (bound effect with row constraints and time with the timeout); a way for a pipeline to extend its own deadline; a partial-progress "resume from where it timed out" — a timed-out run is terminal and re-running it starts fresh (design for idempotency, see below).