Skip to main content

timeout-dead

PyPI version Python Platform License Tests Ruff Pyright

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. Fully cross-platform — 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"

# Capture stderr/stdout with a five-line live tail preview
timeout-dead --capture-output "git diff --stat"

# Run the bundled stdout/stderr demo script with live capture
time-d --capture-output "python tests/example_script.py"

Usage

usage: timeout-dead [-h] [-v] [--sec SECONDS] [--signal SIGNAL] [--no-output]
                    [-c] 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 and override --capture-output
  -c, --capture-output  capture and format stdout/stderr blocks

Default output format:

Running: git status --short --branch
Timeout: 60.0 seconds

Out:

<command output>

Exit code: 0

Captured final output format (--capture-output):

Running: git status --short --branch
Timeout: 60.0 seconds

Err:

<stderr text>

Out:

<stdout text>

Exit code: 0

In capture mode, timeout-dead reads stdout and stderr concurrently while the command is still running. When stdout is an interactive terminal, the Err: and Out: blocks are redrawn as a live tail preview: only the last 5 stderr lines and last 5 stdout lines are shown, so large output does not scroll the whole screen. After the command exits, the preview is cleared and the complete stderr/stdout text is printed in the final format shown above.

The live preview uses a fixed-height local frame and throttled redraws to reduce flicker on fast character-by-character output. The terminal cursor is hidden while the preview is active and restored when the command completes, fails, or is interrupted.

When stdout is redirected or captured by CI/test tools, output stays plain text with no ANSI control sequences. In that mode, timeout-dead prints the same complete final blocks after the command exits.

The default mode intentionally does not capture streams. It inherits the terminal handles so interactive and TUI programs such as vim, less, REPLs, Gradle progress output, and similar tools continue to behave normally. Use --capture-output only when you need separated stdout/stderr blocks in logs or tests.

To see a longer mixed stdout/stderr example locally:

time-d --capture-output "python tests/example_script.py"

Flag priority:

  1. -h / --help and -v / --version exit immediately and ignore all other arguments.
  2. --sec and --signal apply whenever a command is executed.
  3. --no-output suppresses all normal output and has priority over --capture-output.
  4. --capture-output is enabled only when --no-output is not set.
  5. COMMAND is required unless -h / --help or -v / --version is used.

How it works

  1. timeout-dead starts the command in a new process group (Unix) / console group (Windows).
  2. A background timer waits for the specified timeout.
  3. If the command finishes in time, its output and exit code are forwarded.
  4. If the timeout expires:
    • The chosen signal is sent to the process group.
    • After 1 second, if the process is still running, SIGKILL (Unix) or process.kill() (Windows) is sent.
    • A Timeout exceeded message is printed to stderr.

Windows signal note: On Windows, CTRL_BREAK_EVENT (used for TERM and HUP) is sent to the main process only — child processes may not receive it. They will be force-killed after the 1-second grace period. For critical cleanup, use KILL or ensure the parent handles cleanup.

Process group note: Processes that explicitly detach from their process group (e.g., via setsid or nohup) may not be terminated. Even KILL may not reach processes that have created a new session.

Cross-platform

Zero code changes between platforms. timeout-dead detects the OS at startup and uses the native termination strategy:

OS Process isolation Graceful signal Force kill
Linux setsid() process group SIGTERM SIGKILL via killpg()
macOS setsid() process group SIGTERM SIGKILL via killpg()
Windows Job Object (kernel32.dll) CTRL_BREAK_EVENT taskkill /T /F + Job close

Tested on all three platforms in CI.

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"
Interactive REPL vim / nano / less May leave terminal echo disabled after kill timeout-dead --sec 10 --signal INT "vim"

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.

Timeout icons created by pocike - Flaticon

Metadata

Release files for timeout-dead 0.4.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for timeout-dead 0.4.2
File Size Uploaded
timeout_dead-0.4.2.tar.gz 243.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for timeout-dead 0.4.2
File Interpreter ABI Platform
timeout_dead-0.4.2-py3-none-any.whl Python 3 none any Details

Total release size: 265.1 kB

Release files / timeout_dead-0.4.2.tar.gz

Download URL timeout_dead-0.4.2.tar.gz
Size 243.1 kB
Tags Source
SHA-256 checksum
How to use checksums
266c3c79386cedba19ffeb827fbc89d7596e7a400c68337ed70c3098f43d9562
BLAKE2b-256 checksum
How to use checksums
65d9becf67aa67579f7ecc1b47125183d72241bca731a2874f357500881f7cf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release files / timeout_dead-0.4.2-py3-none-any.whl

Download URL timeout_dead-0.4.2-py3-none-any.whl
Size 22.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
73afb3d546d5a2c51da2faa90d4663a4d713c8443b1ae1e3b6cc2b81ffe22321
BLAKE2b-256 checksum
How to use checksums
055d61a193d06b90cf56bf9557c47bae60b02e65b01d4ee96032c9f7cc13ac8d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.1

2 release files

0.5.0

2 release files

0.4.4

2 release files

0.4.3

2 release files

This release

0.4.2 This release

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page