Skip to content

Fix resource leaks and false success in retained sub-agent sessions (CL-7002) - #624

Merged
TheGreatAxios merged 1 commit into
mainfrom
cl-7002-retained-sessions-never-release-and-close_agent-can-report
Aug 24, 2026
Merged

Fix resource leaks and false success in retained sub-agent sessions (CL-7002)#624
TheGreatAxios merged 1 commit into
mainfrom
cl-7002-retained-sessions-never-release-and-close_agent-can-report

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Collaborator

Summary

Four resource-leak / false-success defects in the CL-6943 retained-session lifecycle (session-store.ts, agent-fleet.ts, run.ts):

  1. Every spawn_agent worker leaked by default. pruneCompleted no longer exempts retained-completed sessions from any bound — they now share the same maxCompleted cap every other finished session already used, and eviction releases the session's close handle (LSP sidecars, reactor, @intx/agent lock entry) instead of abandoning it. cancelAll and clear() now also release every still-open retained session directly (not gated on status === "running", which a retained "done" session never satisfies).
  2. close_agent during agent setup falsely reported success. closeOne now waits (bounded by the same deadline) for registerClose to fire if it hasn't yet, instead of finding no handle and returning a false "shutdown". If the window never closes in time it returns the session's honest in-progress status so a retry can still find it.
  3. A disposed salvage looked resumable. complete() takes an agentRetained flag (threaded from RunSubAgentResult.agentRetained, set only on run.ts's clean-completion path when persist actually skipped teardown); a deadline/cancel salvage resolves the same promise but always disposed its agent, so it now clears retained instead of leaving the session resumeOne-eligible.
  4. The parent-abort listener was torn down on the persist path. createSubAgentRunController.dispose() takes a keepParentListener option; a persisting run keeps the forwarding listener alive so a later parent cancel still reaches the registered close handle (which fully disposes the controller once the session actually closes).

Test plan

  • bun test src/subagent/ — 273 pass (includes new src/subagent/retain-salvage.test.ts, copied from the provided starting point and extended with setup-window / clear() / genuinely-resumable cases)
  • bun run check (lint + typecheck + build + full test suite) — 5342 pass, 0 fail

Replaces #623, which sat on a branch name that would have auto-closed CL-7001 (a different ticket). Identical commits.

Retained sessions were exempt from any cap/TTL and never released on
cancelAll/clear; close_agent during agent setup returned false success
over an unreleasable session; a disposed salvage still looked resumable;
and the parent-abort listener was torn down on the persist path,
stranding a retained session's LSP sidecars, reactor, and @intx/agent
lock entry.

- session-store.ts: fold retained-completed sessions into the existing
  maxCompleted bound (no separate cap needed) and release their close
  handle on eviction; cancelAll and clear() now invoke closeHandles for
  every still-open retained session; closeOne waits (bounded) for
  registerClose during the setup window instead of reporting shutdown
  with nothing to release, and reports the honest in-progress status if
  the window never closes in time; complete() only leaves a session
  resumable when the caller confirms the agent actually survived the
  turn (agentRetained), so a disposed salvage can't look reusable.
- run.ts: keep the run controller's parent-abort forwarding alive for a
  persisted session so a later parent cancel still reaches the
  registered close handle; the close handle itself fully disposes the
  controller once the session actually closes.
- types.ts / agent-fleet.ts: thread agentRetained through
  RunSubAgentResult so the store can tell a clean, still-open completion
  apart from a resolved-but-disposed salvage.
@linear-code

linear-code Bot commented Aug 24, 2026

Copy link
Copy Markdown

CL-7001

CL-7002

@TheGreatAxios
TheGreatAxios enabled auto-merge (squash) August 24, 2026 13:51
@TheGreatAxios
TheGreatAxios merged commit 16e1e41 into main Aug 24, 2026
10 checks passed
TheGreatAxios added a commit that referenced this pull request Aug 27, 2026
…624)

Retained sessions were exempt from any cap/TTL and never released on
cancelAll/clear; close_agent during agent setup returned false success
over an unreleasable session; a disposed salvage still looked resumable;
and the parent-abort listener was torn down on the persist path,
stranding a retained session's LSP sidecars, reactor, and @intx/agent
lock entry.

- session-store.ts: fold retained-completed sessions into the existing
  maxCompleted bound (no separate cap needed) and release their close
  handle on eviction; cancelAll and clear() now invoke closeHandles for
  every still-open retained session; closeOne waits (bounded) for
  registerClose during the setup window instead of reporting shutdown
  with nothing to release, and reports the honest in-progress status if
  the window never closes in time; complete() only leaves a session
  resumable when the caller confirms the agent actually survived the
  turn (agentRetained), so a disposed salvage can't look reusable.
- run.ts: keep the run controller's parent-abort forwarding alive for a
  persisted session so a later parent cancel still reaches the
  registered close handle; the close handle itself fully disposes the
  controller once the session actually closes.
- types.ts / agent-fleet.ts: thread agentRetained through
  RunSubAgentResult so the store can tell a clean, still-open completion
  apart from a resolved-but-disposed salvage.
TheGreatAxios added a commit that referenced this pull request Aug 27, 2026
…624)

Retained sessions were exempt from any cap/TTL and never released on
cancelAll/clear; close_agent during agent setup returned false success
over an unreleasable session; a disposed salvage still looked resumable;
and the parent-abort listener was torn down on the persist path,
stranding a retained session's LSP sidecars, reactor, and @intx/agent
lock entry.

- session-store.ts: fold retained-completed sessions into the existing
  maxCompleted bound (no separate cap needed) and release their close
  handle on eviction; cancelAll and clear() now invoke closeHandles for
  every still-open retained session; closeOne waits (bounded) for
  registerClose during the setup window instead of reporting shutdown
  with nothing to release, and reports the honest in-progress status if
  the window never closes in time; complete() only leaves a session
  resumable when the caller confirms the agent actually survived the
  turn (agentRetained), so a disposed salvage can't look reusable.
- run.ts: keep the run controller's parent-abort forwarding alive for a
  persisted session so a later parent cancel still reaches the
  registered close handle; the close handle itself fully disposes the
  controller once the session actually closes.
- types.ts / agent-fleet.ts: thread agentRetained through
  RunSubAgentResult so the store can tell a clean, still-open completion
  apart from a resolved-but-disposed salvage.
@TheGreatAxios
TheGreatAxios deleted the cl-7002-retained-sessions-never-release-and-close_agent-can-report branch August 28, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant