- Adds `runner.post_task_script` and `runner.post_task_script_timeout` (default `5m`) to run a host executable after each task’s built-in cleanup (post-steps, container teardown, bind-workdir removal). - Stops task heartbeats via `Reporter.StopHeartbeats()` while the script runs so Gitea won’t assign overlapping work; the final task acknowledgement still happens in `reporter.Close()`. - Script output goes to the runner process log; non-zero exits are warned only and do not change the job result. - Documents lifecycle, offline behavior, timeouts, and Windows limits (`.ps1` not supported yet) in `docs/post-task-script.md`. Reviewed-on: https://gitea.com/gitea/runner/pulls/1026 Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
7.2 KiB
Post-task script
The post-task script is an optional host hook that runs once after every task, after the runner has already finished its normal per-task cleanup. Typical uses include pruning Docker images, vacuuming ephemeral disks, or resetting VM state between jobs.
It is configured under runner.post_task_script in the runner YAML config (see config.example.yaml).
When it runs
For each task, execution order is:
- Workflow runs (steps, actions, containers).
- In-job cleanup (action
post:steps, container stop/remove). - Job outputs are reported to Gitea.
- Bind-workdir workspace removal, when
container.bind_workdiris enabled. - Post-task script (this hook).
- Final task acknowledgement to Gitea (
reporter.Close()).
The script is additive: it does not replace any built-in cleanup. When container.bind_workdir is enabled, the task workspace directory has usually already been deleted before the script starts. GITEA_WORKSPACE is still set to the path the job used, for reference.
Runner stays offline until the script finishes
This is the most important operational detail.
When the post-task script starts, the runner stops sending task heartbeats to Gitea (the same mechanism used during cancel/cleanup). From Gitea's perspective, the runner is not available for new work until:
- The script exits (success or failure), and
- The runner sends the final task flush to Gitea.
While the script runs:
- Gitea will not assign another task to this runner for the current job slot (heartbeats are stopped).
- The runner capacity slot stays occupied locally — with
capacity: 1, the poller will not start another task until the script completes. - Runner shutdown (
shutdown_timeout) counts this phase as part of the in-flight task; a long or stuck script delays graceful shutdown.
If the script never exits, the runner remains in this state until runner.post_task_script_timeout elapses (default 5 minutes when a script is configured). The runner then kills the script process and proceeds to the final acknowledgement. Until that timeout fires, the runner effectively stays offline.
Set post_task_script_timeout to a value that matches how long your housekeeping is allowed to take — not how long you wish it could take. Prefer short, bounded scripts.
Recommendations
- Keep scripts fast and bounded (seconds, not minutes).
- Avoid interactive prompts, blocking network calls without timeouts, or waiting on user input.
- Use idempotent operations (the script may run after success, failure, or cancellation).
- Test failure modes: hung script, non-zero exit, missing executable.
- Watch the runner process log for script output (it is not written to the Gitea job log).
- On shutdown, ensure scripts respond to process termination within
post_task_script_timeout.
Configuration
runner:
# Path to an executable on the host. Empty or omitted disables the hook.
post_task_script: /usr/local/bin/gitea-post-task.sh
# Hard limit on script runtime. Default when post_task_script is set: 5m.
# If the script exceeds this, it is killed and the runner continues.
post_task_script_timeout: 2m
| Option | Default | Description |
|---|---|---|
runner.post_task_script |
(disabled) | Host path to the script or binary. Relative paths are resolved from the runner process working directory. |
runner.post_task_script_timeout |
5m (only when script is set) |
Maximum time the script may run before the runner kills it and moves on. |
The script must be executable on the host (shebang on Linux/macOS, or a native .exe / .bat / .cmd on Windows). PowerShell (.ps1) is not supported yet as the value of post_task_script; the runner executes the configured path directly and does not invoke powershell.exe for you.
gitea-runner exec does not load runner YAML and will not run this hook.
Environment variables
The script receives runner.envs / runner.env_file values plus:
| Variable | Description |
|---|---|
GITEA_TASK_ID |
Numeric task ID. |
GITEA_RUN_ID |
Workflow run ID, when provided by the server. |
GITEA_REPOSITORY |
Repository slug (owner/name). |
GITEA_WORKSPACE |
Workspace path used for the job (may already be deleted). |
GITEA_JOB_RESULT |
success, failure, cancelled, skipped, or unknown. |
The script environment is not a full copy of the job container environment. System variables such as PATH are only present if you define them in runner.envs or runner.env_file.
Output and errors
- Stdout/stderr are written to the runner process log (logrus), prefixed with
post-task script stdout:/post-task script stderr:. - Non-zero exit codes are logged as warnings only. They do not change the job result already reported to Gitea.
- Timeouts and start failures are logged as warnings; the runner still completes the task acknowledgement.
Interaction with other timeouts
| Timeout | Effect on post-task script |
|---|---|
runner.post_task_script_timeout |
Kills the script if it runs too long. This is the only timeout that bounds the script. |
runner.timeout |
Caps the task up to the script. The script detaches from the task deadline, so a job near the runner timeout limit does not cut the script short — it still gets its full post_task_script_timeout. |
runner.shutdown_timeout |
On SIGINT/SIGTERM, bounds how long the runner waits for the task to finish. The post-task script detaches from cancellation, so it is not interrupted by this window and may extend shutdown until its own post_task_script_timeout elapses. |
Examples
Linux — prune dangling Docker resources
/usr/local/bin/gitea-post-task.sh:
#!/bin/sh
set -eu
docker image prune -f
docker builder prune -f --filter 'until=24h'
config.yaml:
runner:
post_task_script: /usr/local/bin/gitea-post-task.sh
post_task_script_timeout: 3m
Windows — batch file (.cmd)
Use a .cmd or .bat file. PowerShell scripts are not supported yet as post_task_script; call PowerShell from a batch wrapper if needed:
C:\gitea-runner\scripts\post-task.cmd:
@echo off
docker image prune -f
runner:
post_task_script: C:\gitea-runner\scripts\post-task.cmd
post_task_script_timeout: 3m
PowerShell workaround until native .ps1 support exists:
C:\gitea-runner\scripts\post-task.cmd:
@echo off
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0post-task.ps1"
Windows notes
- Supported as
post_task_script:.exe,.bat,.cmd. - Not supported yet:
.ps1as the configured path (use a.cmdwrapper; see above). .shfiles require a Unix shell on the PATH unless you pointpost_task_scriptat the interpreter.- Use backslashes or forward slashes in YAML paths; both work in Go on Windows.
See also
- Configuration — generating and loading
config.yaml - config.example.yaml — all runner options
- Bind-workdir idle cleanup (
runner.workdir_cleanup_age) — separate from this hook; runs only when the runner is idle