Skip to content

REQ-F-011: Default Timeout Per Command

Tier: Framework-Automatic | Priority: P0

Source: §11 Timeouts & Hanging Processes

Addresses: Severity: Critical / Token Spend: High / Time: Critical / Context: Low


Description

The framework MUST apply a default wall-clock timeout to every command execution. The default value MUST be configurable at the framework level and overridable per command. A command whose run length depends on its input declares a per-command default sized for its expected inputs, since a timeout on it is not cleared by re-running the identical invocation (REQ-C-014). A command that exceeds its timeout MUST be terminated by the framework, not left to run indefinitely. The timeout MUST be enforced even if the command itself does not implement any timeout logic.

A command that declares interruption (REQ-C-033) is still bounded, but a run that outlasts one call is not killed: the sync call budget (REQ-F-080) bounds each call, and the wall-clock limit bounds the whole work across its continuations. Without an explicit --timeout, that limit is the command's interruption.max_lifetime_ms, and a background job that stops making progress ends after interruption.idle_timeout_ms (REQ-F-081). For such a command, the per-command default sized for its expected inputs is interruption.max_lifetime_ms, not a per-call limit, and it never needs --timeout 0 to survive a large input.

Acceptance Criteria

  • A command that sleeps indefinitely exits within default_timeout + 5s without manual intervention
  • The framework's timeout mechanism works even if the command's code is blocked on I/O
  • The timeout value used for each execution is recorded in meta.timeout_ms

Schema

Types: response-envelope.md

The ResponseMeta object carries timeout_ms to record the configured limit for the execution. This field is always populated by the framework, regardless of whether the timeout was reached.


Wire Format

meta.timeout_ms appears in every response, recording the configured timeout for that execution:

{
  "ok": true,
  "data": { "status": "synced", "records": 42 },
  "error": null,
  "warnings": [],
  "meta": {
    "exit_code": 0,
    "duration_ms": 1240,
    "timeout_ms": 30000
  }
}

Example

Framework-Automatic: no command author action needed. The framework wraps every command invocation in a timeout enforcer and records the configured limit in meta.

# Framework-level default (e.g., 30s)
app = Framework("tool", default_timeout_ms=30000)

# Per-command override at registration
register command "import":
  timeout_ms: 300000   # 5 minutes for long-running import

# Every response includes the active timeout limit
meta.timeout_ms = 300000   # import command
meta.timeout_ms = 30000    # all other commands

Requirement Tier Relationship
REQ-F-012 F Specializes: defines the error response and exit code when the timeout fires
REQ-F-004 F Composes: timeout_ms is added to ResponseMeta in the standard envelope
REQ-F-001 F Provides: TIMEOUT (10) is the exit code emitted when the limit is exceeded
REQ-C-001 C Composes: commands that can time out must declare TIMEOUT (10) in their exit code map
REQ-C-032 C Specializes: a protocol command's timeout stops when serving begins
REQ-F-080 F Composes: the budget bounds one call; this limit bounds the whole work
REQ-F-081 F Specializes: an interruptible command's limit is its job lifetime, with an idle limit beside it