feat: command timeouts (#2898)

This commit is contained in:
Valentin Maerten
2026-08-11 22:09:25 +02:00
committed by GitHub
parent 993508c782
commit 37898d9102
21 changed files with 830 additions and 50 deletions

View File

@@ -847,6 +847,7 @@ tasks:
platforms: [linux, darwin]
set: [errexit]
shopt: [globstar]
timeout: 5m
```
### Task References
@@ -963,6 +964,58 @@ tasks:
if: '[ "{{.ITEM}}" != "b" ]'
```
### Command Timeouts
Use `timeout` to limit how long a command may run. The value uses Go duration
syntax (e.g. `30s`, `5m`, `1h30m`) and must be greater than zero.
```yaml
tasks:
deploy:
cmds:
- cmd: npm run build
timeout: 5m
- cmd: ./deploy.sh
timeout: 30m
```
When a command exceeds its timeout, it is terminated and the task fails with an
error, preventing commands from hanging indefinitely in a pipeline. The timeout
bounds the whole step, so an [`if`](#command) condition that hangs is cut short
too, and [`ignore_error`](#command) covers a timeout like any other failure. A
timed-out command reports [`EXIT_CODE`](/docs/reference/templating#exit_code)
`124`, following the convention of `timeout(1)`.
A dependency takes the same key:
```yaml
tasks:
build:
deps:
- task: fetch-assets
timeout: 2m
```
The key goes next to the command whatever form it takes, including a `defer`:
```yaml
tasks:
deploy:
cmds:
- defer:
task: cleanup
timeout: 30s
- defer: ./cleanup.sh
timeout: 30s
```
A timed-out deferred command is logged and ignored, like other deferred errors.
Calling a task that is already running under [`run: once`](#task) or
[`run: when_changed`](#task) joins that execution instead of starting a second
one. A `timeout` on such a call bounds how long you wait for it, not the shared
execution itself, which only the caller that started it can bound.
## Shell Options
### Set Options

View File

@@ -334,7 +334,8 @@ tasks:
- **Type**: `int`
- **Description**: Failed command exit code (only in `defer`, only when
non-zero)
non-zero). A command killed by its [`timeout`](/docs/reference/schema#command)
is reported as `124`, following the convention of `timeout(1)`.
```yaml
tasks:

View File

@@ -356,6 +356,10 @@
"if": {
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
"type": "string"
},
"timeout": {
"description": "Maximum duration the command is allowed to run before being terminated. Supports Go duration syntax (e.g., '5m', '30s', '1h').",
"type": "string"
}
},
"additionalProperties": false,
@@ -397,11 +401,38 @@
"if": {
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
"type": "string"
},
"timeout": {
"description": "Maximum duration the command is allowed to run before being terminated. Supports Go duration syntax (e.g., '5m', '30s', '1h').",
"type": "string"
}
},
"additionalProperties": false,
"required": ["cmd"]
},
"deferred_task_call": {
"type": "object",
"properties": {
"task": {
"description": "Name of the task to run",
"type": "string"
},
"vars": {
"description": "Values passed to the task called",
"$ref": "#/definitions/vars"
},
"silent": {
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`.",
"type": "boolean"
},
"if": {
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
"type": "string"
}
},
"additionalProperties": false,
"required": ["task"]
},
"defer_task_call": {
"type": "object",
"properties": {
@@ -409,9 +440,13 @@
"description": "Run a command when the task completes. This command will run even when the task fails",
"anyOf": [
{
"$ref": "#/definitions/task_call"
"$ref": "#/definitions/deferred_task_call"
}
]
},
"timeout": {
"description": "Maximum duration the command is allowed to run before being terminated. Supports Go duration syntax (e.g., '5m', '30s', '1h').",
"type": "string"
}
},
"additionalProperties": false,
@@ -427,6 +462,10 @@
"silent": {
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`.",
"type": "boolean"
},
"timeout": {
"description": "Maximum duration the command is allowed to run before being terminated. Supports Go duration syntax (e.g., '5m', '30s', '1h').",
"type": "string"
}
},
"additionalProperties": false,
@@ -471,6 +510,10 @@
"if": {
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
"type": "string"
},
"timeout": {
"description": "Maximum duration the command is allowed to run before being terminated. Supports Go duration syntax (e.g., '5m', '30s', '1h').",
"type": "string"
}
},
"additionalProperties": false,
@@ -505,6 +548,10 @@
"if": {
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
"type": "string"
},
"timeout": {
"description": "Maximum duration the command is allowed to run before being terminated. Supports Go duration syntax (e.g., '5m', '30s', '1h').",
"type": "string"
}
},
"additionalProperties": false,
@@ -527,6 +574,10 @@
"vars": {
"description": "Values passed to the task called",
"$ref": "#/definitions/vars"
},
"timeout": {
"description": "Maximum duration the command is allowed to run before being terminated. Supports Go duration syntax (e.g., '5m', '30s', '1h').",
"type": "string"
}
},
"additionalProperties": false,