Runlane
Build with RunlaneControl work

Limit task runtime

Give each attempt a fixed deadline and stop timed-out work cooperatively.

Use maxAttemptDuration when one task attempt must not run forever. The deadline starts when the attempt is claimed.

Set the task deadline

src/tasks/rebuild-search-index.ts
import { task } from '@runlane/core'
import * as z from 'zod'

const env = z.object({ SEARCH_API_URL: z.url() }).parse(process.env)

export const rebuildSearchIndex = task({
  id: 'search.rebuild',
  schema: z.object({ indexId: z.string() }),
  maxAttemptDuration: '10m',
  async run({ indexId }, context) {
    const response = await fetch(new URL(`/indexes/${encodeURIComponent(indexId)}/rebuild`, env.SEARCH_API_URL), {
      method: 'POST',
      signal: context.signal,
    })
    if (!response.ok) throw new Error(`Search API returned ${response.status}`)
  },
})

The deadline covers the handler and asynchronous output validation. When it expires, Runlane aborts context.signal and records TaskTimedOut.

A timeout follows the task's retry policy. If a policy is set and attempts remain, the run becomes retrying. Otherwise it ends as failed with ErrorCode.TaskTimedOut. Omit retry when a timeout should require an explicit operator or application decision before starting the work again.

Cooperate with the signal

JavaScript cannot be stopped safely from the outside. Pass context.signal to HTTP clients, database calls, child processes, and other APIs that support AbortSignal.

Check cancellation before starting another side effect. Use your own resource limits for dependencies that ignore signals.

Override at an execution boundary

runNow(), delivery handlers, and workers accept a maxAttemptDuration override. The execution override takes precedence over the task value.

Use an override for a deployment-wide ceiling. Keep the task value for the normal business limit.

Understand dead owners

The fixed deadline is stored with the active attempt. If a worker disappears or ignores the abort, maintenance can finalize the timeout after ownership expires.

When both the lease and fixed deadline are due, timeout finalization wins. Reacquiring the run first would erase the durable timeout outcome.

Verify the boundary

  • Use a controlled clock instead of sleeping.
  • Confirm the task signal aborts at the deadline.
  • Confirm the stored failure code is TaskTimedOut.
  • Test a handler that ignores abort and let maintenance finalize it.
  • Test output validation that crosses the deadline.

Use cancellation for operator-requested stops and retries for retryable dependency failures.

On this page