Lightweight command timeout utility with zero runtime dependencies
Project description
timeout-dead
Lightweight command timeout utility with zero runtime dependencies.
Runs any shell command with a configurable time limit and termination signal.
If the command exceeds the timeout, timeout-dead sends the chosen signal,
waits a 1-second grace period, then force-kills the process.
Works on Linux, macOS, and Windows (Git Bash / WSL).
Installation
pip install timeout-dead
Or via uv:
uv tool install timeout-dead
Requires Python 3.10 or later. Zero runtime dependencies — pure Python standard library.
Quick start
# Run a command with default 60s timeout
timeout-dead "python -c 'print(42)'"
# Short alias also works
time-d "echo hello"
# Specify a custom timeout
timeout-dead --sec 120 "npm run build"
# Sub-second timeouts work too
timeout-dead --sec 0.5 "potentially-hanging-tool"
# Use SIGINT instead of default SIGTERM
timeout-dead --signal INT --sec 30 "long-running-server"
# Run silently — suppress all normal output
timeout-dead --no-output "curl -s https://example.com"
Usage
usage: timeout-dead [-h] [-v] [--sec SECONDS] [--signal SIGNAL] [--no-output] COMMAND ...
Lightweight command timeout utility.
positional arguments:
COMMAND command to execute
options:
-h, --help show this help message and exit
-v, --version show version and exit
--sec SECONDS timeout in seconds (default: 60.0, accepts floats)
--signal SIGNAL signal to send on timeout (TERM, KILL, HUP, INT)
--no-output suppress normal output (stdout, stderr, header, footer)
How it works
timeout-deadstarts the command in a new process group (Unix) / console group (Windows).- A background timer waits for the specified timeout.
- If the command finishes in time, its output and exit code are forwarded.
- If the timeout expires:
- The chosen signal is sent to the process group.
- After 1 second, if the process is still running,
SIGKILL(Unix) orprocess.kill()(Windows) is sent. - A
Timeout exceededmessage is printed to stderr.
Signal reference
| Signal | Unix | Windows |
|---|---|---|
TERM |
SIGTERM (15) — terminate gracefully |
CTRL_BREAK_EVENT — console break |
KILL |
SIGKILL (9) — force kill |
Falls back to TerminateProcess |
HUP |
SIGHUP (1) — hangup |
Falls back to TerminateProcess |
INT |
SIGINT (2) — interrupt (Ctrl+C) |
CTRL_C_EVENT — console interrupt |
Why subprocess timeout is not enough
Python's built-in subprocess timeout only kills the direct child, not its entire process tree. If your command spawns subprocesses (npm install, make, docker build), children survive the parent kill.
timeout-dead uses process groups to terminate everything — every subprocess, pipeline, and child.
Real-world scenarios
| Scenario | Command | Why it hangs | timeout-dead |
|---|---|---|---|
| Gradle build | ./gradlew build |
20+ min fresh build, agent generates duplicate commands | timeout-dead --sec 600 "./gradlew build" |
| CMake build | cmake --build . |
Locks waiting for dependency resolution | timeout-dead --sec 180 "cmake --build ." |
| Spring Boot | ./gradlew bootRun |
Server never exits, agent won't proceed | timeout-dead --sec 30 --signal INT "./gradlew bootRun" |
| Docker build | docker build -t myapp . |
Network timeout, internal retries, no progress | timeout-dead --sec 600 "docker build -t myapp ." |
| npm install | npm install |
Corrupted cache or registry auth hang | timeout-dead --sec 300 "npm install" |
| Interactive REPL | python / node / irb |
Waits for input, agent doesn't know | timeout-dead --sec 5 "python" |
For AI agents
If you build agents that execute shell commands, timeout-dead is essential infrastructure. Agents frequently generate commands that hang — waiting for input, entering infinite loops, or starting interactive programs.
Instead of agents freezing indefinitely, wrap every command:
timeout-dead --sec <timeout> --signal <signal> "<command>"
No dependencies, no code changes. Agent always gets a response — exit code + output — and can implement retry, fallback, or user notification.
Development
git clone https://github.com/UmbrellaLeaf5/timeout-dead
cd timeout-dead
uv sync --extra dev
uv run pytest tests/ -v
uv run ruff check src/timeout_dead/ tests/
uv run ruff format --check .
uv run pyright src/timeout_dead/
License
Unlicense — public domain.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file timeout_dead-0.2.0.tar.gz.
File metadata
- Download URL: timeout_dead-0.2.0.tar.gz
- Upload date:
- Size: 71.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54438361f963b216dc2f07c40493bef5f73d76f34a9c5fc7f826508e169a2e34
|
|
| MD5 |
e15342999499a705882c3a0886cbf2e1
|
|
| BLAKE2b-256 |
a1c70a6bdd83c2590e2ca6422a637baf27a2736423479fe4e4bca95d5dc16840
|
Provenance
The following attestation bundles were made for timeout_dead-0.2.0.tar.gz:
Publisher:
publish.yml on UmbrellaLeaf5/timeout-dead
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
timeout_dead-0.2.0.tar.gz -
Subject digest:
54438361f963b216dc2f07c40493bef5f73d76f34a9c5fc7f826508e169a2e34 - Sigstore transparency entry: 2240546256
- Sigstore integration time:
-
Permalink:
UmbrellaLeaf5/timeout-dead@d1dae1c5b5058e1f7fb05fb9f236c8e0201bb8b8 -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/UmbrellaLeaf5
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d1dae1c5b5058e1f7fb05fb9f236c8e0201bb8b8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file timeout_dead-0.2.0-py3-none-any.whl.
File metadata
- Download URL: timeout_dead-0.2.0-py3-none-any.whl
- Upload date:
- Size: 8.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1aadd5554daa021feee7901024f7da11df594588cc82c405bb13603987db3863
|
|
| MD5 |
f5d847ec475d731460682f772db647c6
|
|
| BLAKE2b-256 |
29d87366983c71633b585667aadb9ea9d9199e84fadec4f8437fa05ad0a74cfc
|
Provenance
The following attestation bundles were made for timeout_dead-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on UmbrellaLeaf5/timeout-dead
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
timeout_dead-0.2.0-py3-none-any.whl -
Subject digest:
1aadd5554daa021feee7901024f7da11df594588cc82c405bb13603987db3863 - Sigstore transparency entry: 2240546646
- Sigstore integration time:
-
Permalink:
UmbrellaLeaf5/timeout-dead@d1dae1c5b5058e1f7fb05fb9f236c8e0201bb8b8 -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/UmbrellaLeaf5
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d1dae1c5b5058e1f7fb05fb9f236c8e0201bb8b8 -
Trigger Event:
push
-
Statement type: