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 + 5swithout 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
Related
| 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 |