fleet-guard
Machine-resource guard layer for fleets of coding-agent sessions (Claude Code and friends).
When you run many agent sessions in parallel, the failure mode is rarely a bad
prompt — it's the machine falling over: background processes leak and pile
up, a hung run burns quota for hours while doing nothing, a session ends and
leaves orphaned children behind, a scratch dir quietly eats the disk overnight.
fleet-guard is a small stdlib-only Python package — three Claude Code hooks, a
stall watchdog daemon, a disk guard, and an append-only JSONL ledger — that
keeps the fleet from taking down the host.
What it does
- Cross-session background-process quota — a
PreToolUsehook counts agent-attributed background processes machine-wide. Over the cap (default: half your CPU cores), the new background run is blocked with a message naming what's already running. - Stall watchdog — register long-running processes with
fleet-guard watch add. If a watched process is alive but its log goes quiet for N seconds (default 600), the watchdog captures a diagnostic snapshot and kills the whole process tree — children first,SIGTERM, thenSIGKILLfor survivors. - SessionEnd reaping — a
SessionEndhook kills leaked child process trees the session left behind. - Disk guard — alerts (and optionally quarantines) when agent scratch dirs grow past a threshold, or their filesystem runs low on free space.
- Append-only ledger — every guard action lands in
~/.fleet-guard/ledger.jsonl.
Fail-open by design. Every hook and daemon pass is wrapped so that a guard that crashes exits 0 and lets the agent continue. A guard must never take down the agent it's protecting.
How this differs
- vs
loop-guard(my semantic layer):loop-guarddetects semantic loops — an agent stuck in churn/test/burn review cycles, repeating itself without progress.fleet-guardis the machine-resource layer underneath it: processes, stalls, leaked children, disk. An agent can be semantically fine and still wedge the machine (a hungsleep-forever burns no tokens but holds a slot; a runaway log fills the disk). The two are complementary — run both. - vs manual shell workarounds (
session-tree.shscripts, hand-rolled cgroups,pkillcron jobs): those work, but they're glue you maintain per machine.fleet-guardis one installable package — hooks, daemon, ledger — with a single config surface and tests.
Motivating case class
The design is informed by Ramiz Raja's production checklist for running
multiple Claude Code agents
(codebyramiz.com/blog/running-multiple-claude-code-agents,
Oct 2026): cap background processes at half the cores, alert when too many are
busy, reap leaked processes on session end, and run a watchdog that snapshots
and kills a process tree after 600s of log silence. His watchdog once caught a
run hung for 7h22m while using only 258ms of CPU — invisible to any
CPU-based monitor, obvious to a log-liveness check. His disk policy came from
an overnight 56G → 1G incident. fleet-guard packages exactly that
operational pattern so you don't have to re-derive it the hard way.
Install
pip install fleet-guard
Python 3.9+, stdlib only — no dependencies. Linux is the primary target
(/proc-based inspection); macOS works via a ps fallback (environment
attribution is Linux-only).
Hook setup (Claude Code)
Add to your Claude Code hooks config:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "fleet-guard hook pretooluse" }] }
],
"SessionEnd": [
{ "hooks": [{ "type": "command", "command": "fleet-guard hook sessionend" }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "fleet-guard hook stop" }] }
]
}
}
Attribute background runs to a session so the quota and reaper can see them:
fleet-guard run --session "$CLAUDE_SESSION_ID" -- ./long-build.sh > build.log 2>&1 &
fleet-guard watch add --pid $! --log build.log --timeout 600 --label "build"
Usage
# watch a process's log for stalls (log mtime = liveness signal)
fleet-guard watch add --pid 1234 --log ./build.log --timeout 600 --session abc
fleet-guard watch list
fleet-guard watch rm --pid 1234
# run the watchdog daemon (or tick `watchdog-once` from cron)
fleet-guard daemon --interval 30
fleet-guard watchdog-once
# check agent scratch dirs against disk limits
fleet-guard disk-check
# inspect what the guard did
fleet-guard ledger -n 20
Configuration
Defaults live in code; override with ~/.fleet-guard/config.json or
FLEET_GUARD_* environment variables:
| Key | Default | Env |
|---|---|---|
bg_quota |
half CPU cores | FLEET_GUARD_BG_QUOTA |
busy_alert_threshold |
4 | FLEET_GUARD_BUSY_ALERT |
stall_timeout_s |
600 | FLEET_GUARD_STALL_TIMEOUT |
watch_interval_s |
30 | FLEET_GUARD_WATCH_INTERVAL |
kill_grace_s |
5 | FLEET_GUARD_KILL_GRACE |
scratch_dirs |
[] | FLEET_GUARD_SCRATCH_DIRS (pathsep-joined) |
disk_max_bytes |
10 GB | FLEET_GUARD_DISK_MAX_BYTES |
disk_min_free_bytes |
5 GB | FLEET_GUARD_DISK_MIN_FREE |
disk_quarantine |
false | FLEET_GUARD_DISK_QUARANTINE=1 |
state_dir |
~/.fleet-guard |
FLEET_GUARD_STATE_DIR |
Ledger format
~/.fleet-guard/ledger.jsonl, one JSON object per line:
{"ts": "2026-10-21T19:00:00+00:00", "event": "watchdog.stall-kill",
"session_id": "abc", "details": {"pid": 1234, "idle_s": 623.4,
"dump": "/home/you/.fleet-guard/dumps/stall-1234-1729530000.json", "sigkilled": []}}
Events: hook.pretooluse-block, hook.stop-busy-alert, sessionend.reap,
watch.add, watch.exit, watchdog.stall-kill, disk.alert.
A note on "thread dumps"
The stall snapshot captures everything reachable from outside the target
process with the stdlib on Linux: cmdline, /proc/<pid>/status, wait channel,
kernel stack (/proc/<pid>/stack), IO counters, open FD count, exe/cwd. That
is usually enough to tell "deadlocked on a futex" from "waiting on network
IO". A true in-process Python thread dump (every thread's Python-level stack)
needs cooperation from inside the target — tools like py-spy or gdb —
which is deliberately out of scope for a zero-dependency guard. If you need
that, point the dump path at your py-spy wrapper.
Development
python -m unittest discover -s tests -v # 11 tests, no network
License
MIT — see LICENSE.
Metadata
Release files for fleet-guard 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fleet_guard-0.1.0.tar.gz | 19.7 kB | Without provenance | Details for fleet_guard-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fleet_guard-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for fleet_guard-0.1.0-py3-none-any.whl |
Total release size: 37.6 kB
Release files / fleet_guard-0.1.0.tar.gz
| Download URL | fleet_guard-0.1.0.tar.gz |
|---|---|
| Size | 19.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
de5bf4f7da9efb5db7fb39215f534403cc5ae77f245fe80d72d25b107fa4b003
|
|
BLAKE2b-256 checksum How to use checksums |
1ae32688f2393027cd24e66d62d5ba5577bbdfed97daf517bf0c663344f6f68c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / fleet_guard-0.1.0-py3-none-any.whl
| Download URL | fleet_guard-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c91131f6cb19b2dba5690b6f794e8ac0eb811fae54a6400229b5235eb0481cbb
|
|
BLAKE2b-256 checksum How to use checksums |
77e7a3debf1ef125e0e37efd83a45f873f25b88394cc962595843cbdc0ee0488
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for fleet-guard 0.1.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.