---
title: Abandoned helper processes are cleaned up
---

# Abandoned helper processes are cleaned up

## What it is

When an agent runs a command that takes too long — a test run, a type check, a rebase — the agent's
tool call gives up after a few minutes, but the command it started does not stop. The shell that
launched it and everything under it (test workers, compilers, git) keep running with nobody waiting
for the result, until Omniscio itself exits. On a busy day dozens of these pile up: measured on
2026-09-08, 30 abandoned trees at once, together holding hundreds of processes and gigabytes of memory,
which is a big part of why the whole machine feels slow.

Omniscio now finds these trees and, once you turn enforcement on, ends them.

## Where to find it

There is no panel. The sweep runs in the background and writes its census to a durable log you read from the command line; enforcement is a setting, so turning it on is a deliberate step rather than a switch in the UI.

## How it behaves

### How it decides (the conservative filter)

Every two minutes the existing orphan sweep looks at every process and keeps ONLY trees that pass all
of these:

- **The launcher is gone** — the shell that started the tree has exited (or been replaced by an unrelated
  process that reused its id).
- **It is a tool-call shape** — the root is the `npm`, `npx` or `git` helper an agent's command goes
  through, or the agent's own shell wrapper. Servers, brokers, watchers and anything you started yourself
  are never even considered.
- **It is older than 15 minutes** — longer than the longest wait an agent's tool call can have, so nothing
  still being waited on can qualify.
- **The script is a short-lived kind** — a test, build, check, rebase, translation, cloud or similar
  family. A dev server, a watcher, a mock server or any name Omniscio has not seen before is left alone
  and recorded so the list can grow on evidence.
- **No gate owns it** — nothing that belongs to a running cloud check or a registered gate watch.
- **No git work is in flight** — no rebase, merge, commit or checkout anywhere in the tree, and no
  half-finished git operation in the worktree it names.
- **It belongs to a session** (enforcement only) — the tree sits inside Omniscio's session Job Object.

Anything that fails one test is skipped with the reason recorded.

### Shadow first: read the census before turning it on

Out of the box nothing is ended. The sweep only writes what it WOULD have ended, and why, to a durable
log. Read it with:

```
npm run orphans:status
npm run orphans:status -- --hours=72 --json
```

It shows the verdict counts, the skip reasons, the shapes and scripts involved, the "shadow duty" (how
many trees and processes would have been cleaned up), and warnings when something upstream is off (an
empty session Job, an unreadable gate-watch store).

### The other half of the same question: shells that ran un-contained

The same report ends with a section headed **"Uncontained shells (trampoline)"**, and it answers the
mirror-image question. Every command an agent runs goes through a small wrapper that puts the shell
into a job Omniscio can end as a unit — so if the command is killed, everything under it goes with it.
That wrapper is deliberately **fail-open**: if it cannot build that job it runs the command anyway,
because refusing to run an agent's command would be worse than the risk. The command still runs, and
the failure is now written to `~/.amc/trampoline-uncontained.jsonl` (capped, so it cannot grow without
limit) and reported here.

Without that section the two states are indistinguishable — a shell with no job looks exactly like one
with a job — so the only way to learn that containment had stopped working was to notice an orphan
later. The reason is named on every row:

| Reason | What it means |
| --- | --- |
| `job-create-failed` | Windows refused to create the job object. |
| `job-limit-failed` | The job was created but the kill-on-close limit would not arm — the job is discarded rather than used unarmed. |
| `job-assign-failed` | The shell could not be placed in the job. |
| `forced-by-env` | **Test seam only.** Set by `AMC_TRAMPOLINE_FORCE_UNCONTAINED`, which nothing in production ever sets; it exists so the recording path itself can be proved against the real binary. |

A non-empty list here does **not** mean an orphan exists — it means the net had a hole for that one
command, and it is worth knowing which commands hit it before the next one escapes.

### Turning enforcement on

Watch the shadow census for a full day. If every "would-reap" row is a tree you would have ended by
hand, flip the setting `orphanToolTreeReaperEnforce` to `true` (a settings change, approval-gated like
any other — `PATCH /settings/orphanToolTreeReaperEnforce` with an approval note over the CLI server).
Flip it back the same way at any time. To stop the whole pass, set `AMC_DISABLE_ORPHAN_TOOL_TREE_REAP=1`.

With enforcement on, each tree is re-checked against a fresh process census immediately before it is
ended (same process id, same start time; the command line is consulted only when a start time is
unknown), at most ten trees per sweep, and a tree
whose kill fails three times is set aside rather than retried forever. A sweep that ends ten or more
trees at once raises one informational inbox card, because a recurring storm means something upstream
is still stranding them.

### The one-off command

The same classifier is available by hand:

```
npm run orphans:reap                 # dry run: every candidate with its verdict and the reason
npm run orphans:reap -- --enforce    # end the trees that pass, after re-checking each one
```

Outside the running app the session Job cannot be seen, so the hand-run prints that condition as
`n/a` and relies on the helper shape instead — read the dry run in full before enforcing.

### What is never touched

Anything whose launcher is still alive; any process that is not one of the four helper shapes; anything
younger than 15 minutes; dev servers, watchers, monitors and unknown script names; anything a gate or
gate watch owns; any tree with git write work or a half-finished git operation; and, under enforcement,
anything outside the session Job.

Related: [Job Object orphan-kill (Windows)](job-object-orphan-kill.md) — why these trees survive until
the app exits in the first place.

## Related

- [slow-computer.md](slow-computer.md) — the wider "my computer feels slow while agents run" guide.
- [perf-control.md](perf-control.md) — the screen that shows what is currently being paced on this machine and why.
- [orphan-worktree-archive.md](orphan-worktree-archive.md) — the other background job that reclaims what nothing is using any more.

