Skip to content

Claude subagent watchers never idle-exit: leaked kcap watch processes accumulate for the life of the parent session #514

Closed

Description

@alexeyzimarev

Summary

Claude subagent (child) watchers have no idle-timeout backstop. ShouldEndOnIdle is gated to codex/antigravity/cursor, so a Claude child watcher's only exit paths are the StopWatcher signal driven by SubagentStop and the parent-exit watchdog. If SubagentStop is missed for any reason, the watcher survives until the entire parent Claude process quits — which for a long-lived session means days.

https://github.com/kurrent-io/kcap-cli/blob/main/src/Capacitor.Cli/Commands/WatchCommand.cs#L1069

if (vendor != "codex" && vendor != "antigravity" && vendor != "cursor") return false;

#140 fixed the dominant cause of missed SubagentStop (fd inheritance hanging the hook, documented at ProcessHelpers.cs:279-290). This issue is about the missing backstop: when a SubagentStop is missed for any other reason — interrupted/aborted subagent, crashed hook, killed shell — nothing reaps the watcher.

Evidence

Observed on kcap 0.11.16 (macOS ARM64), from a live process table. One Claude session (b1e7a6ed…, open 12d 23h) owned 9 kcap watch processes: 1 legitimate session watcher plus 8 subagent watchers whose subagents had finished days earlier. Parent PID was alive in every case, so the parent-exit watchdog correctly never fired.

Watcher age vs. last write to the subagent transcript it was watching:

watcher age subagent transcript last written
11d 21h Jul 29 21:51
10d 01h Jul 31 18:11
9d 15h Aug 1 04:34
7d 22h Aug 2 20:25
7d 00h Aug 3 17:16
6d 16h Aug 4 01:34
1d 13h Aug 9 04:28

Roughly 8 leaked out of ~170 subagent transcripts for that session (~5%), so the miss is intermittent rather than systematic — consistent with the "disrupted flow never fires the matching SubagentStop" path rather than a wholesale failure.

Each idle watcher holds ~40 MB RSS and ~0% CPU, so the symptom is memory and process-table pressure, not load. Across a machine running 11 concurrent agent sessions the leaked watchers were ~280 MB.

Impact

  • Unbounded process/RSS growth proportional to session lifetime × subagent count.
  • Likely leaves the corresponding server-side sessions stuck Active, since the watcher never posts its end.
  • Scales badly with the long-lived-session + heavy-subagent-fan-out workflow the product encourages.

Suggested fix

Give Claude child watchers a bounded lifetime that does not depend on the parent process. Two options:

  1. Extend the idle ceiling to Claude child watchers. The Cursor child-watcher exemption at WatchCommand.cs:1080-1084 already handles the "child watchers never buffer, so ThresholdReached never flips" problem — the same carve-out would apply. Cheapest change, reuses tested policy.
  2. Hard ceiling on child-watcher lifetime independent of vendor, on the grounds that a subagent is inherently bounded work and a child watcher outliving its transcript by hours is always wrong.

Option 1 is probably right — it keeps one idle policy rather than adding a second concept. Either way the tool-in-flight suppression already in ShouldEndOnIdle should be retained so a genuinely long-running subagent tool call isn't cut off.

Worth confirming whether the session watcher's end-synthesis semantics should apply to a child watcher exiting on idle, the way CursorSuppressesEndPost scopes it for Cursor.

Repro

Not deterministic (depends on hitting a missed SubagentStop). Observable in aggregate: run a Claude session with heavy subagent fan-out for several days, then compare kcap watch --agent-id process ages against the mtimes of the agent-*.jsonl files they reference.

Activity

  1. linear-code commented on Aug 10, 2026

    @linear-code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions