Skip to content

fix(abort): wire /abort + sessions.reset/delete to killSessionRun — actually terminate CLI subprocesses#2462

Merged
alexey-pelykh merged 1 commit intomainfrom
fix/2343-abort-session-run-kill
Apr 22, 2026
Merged

fix(abort): wire /abort + sessions.reset/delete to killSessionRun — actually terminate CLI subprocesses#2462
alexey-pelykh merged 1 commit intomainfrom
fix/2343-abort-session-run-kill

Conversation

@alexey-pelykh
Copy link
Copy Markdown

Summary

The user-facing /abort command and the gateway sessions.reset / sessions.delete methods lost their subprocess-termination semantics in the pi-embedded gut (b27cecc795, #76/#77). Both call sites were rewiring points for ChannelBridge; replacements (killSessionRun + waitForSessionRunEnd) exist in src/agents/session-run-registry.ts but were never wired to them — they left behind three const aborted = false; / const ended = true; dead placeholders.

This PR wires the three call sites. Same family as #2460 (gateway capacity accounting) and #2461 (subagent kill/steer), completing the trio of session-run-registry integrations that were outstanding after the gut.

Changes

File Site Change
src/auto-reply/reply/abort.ts stopSubagentsForRequester inner loop (line ~248) const aborted = false;const aborted = killSessionRun(childKey); (flows into killed aggregate)
src/auto-reply/reply/abort.ts tryFastAbortFromMessage (line ~329) const aborted = false;const aborted = killSessionRun(resolvedTargetKey); (now returned in the aborted field of the public result)
src/gateway/server-methods/sessions.ts ensureSessionRuntimeCleanup (line ~178) Replace const ended = true; placeholder with killSessionRun(canonicalKey) + await waitForSessionRunEnd(canonicalKey, SESSION_RUN_TERMINATION_TIMEOUT_MS). Return UNAVAILABLE on timeout. Also removes obsolete if (!params.sessionId) return undefined; early-exit (termination operates on canonicalKey, not sessionId).

Plus one import per file and a new SESSION_RUN_TERMINATION_TIMEOUT_MS = 15_000 constant grouped with the sibling ACP_RUNTIME_CLEANUP_TIMEOUT_MS.

Note on stale AC3

Issue #2343 lists three regression sites. The third (commands-session.ts::applyAbortTarget) does not exist in the current treegit grep applyAbortTarget returns zero hits. The /session abort pathway was consolidated into abort.ts::tryFastAbortFromMessage at some earlier point, so no duplicate wiring is needed. AC3 marked N/A.

Behavior change (intended, per AC)

sessions.reset / sessions.delete:

  • Before: returned success instantly regardless of active subprocess (const ended = true; placeholder).
  • After: wait up to 15s for the CLI subprocess to terminate. On success → returns undefined (success). On timeout → UNAVAILABLE error with the pre-gut message "Session ${params.key} is still active; try again in a moment.".

/abort command (non-ACP sessions):

  • Before: marked abortedLastRun = true and cleared queues, but the CLI subprocess kept running until natural completion.
  • After: dispatches abortController.abort() or process.kill(pid, "SIGTERM") via killSessionRun; the aborted field in the return value now reflects reality.

Key-format alignment

killSessionRun keys on canonical sessionKey. All three call sites already operate in canonical space:

  • abort.ts subagent loop: childKey from parseAgentSessionKey (canonical)
  • abort.ts fast-abort: resolvedTargetKey = key ?? targetKey from resolveSessionEntryForKey (canonical)
  • sessions.ts: params.target.canonicalKey (canonical by construction via resolveGatewaySessionStoreTarget)

No conversion risk. Inherits the registration-vs-kill key discipline established in PRs #2460 / #2461.

Verification

  • pnpm check (format + tsgo + lint + project-specific lints) → exit 0
  • pnpm test (full parallel suite) → 800 files / 7010 passed / 3 skipped — no regression
  • Rescans:
  • Adversarial validation (fresh-context subclaude, 7 AC + 9 adversarial checks): CLEAN. Confirmations:
    • waitForSessionRunEnd is LIVE (50ms poll tick, max 15s wall-clock, non-blocking async)
    • ensureSessionRuntimeCleanup callers always pass a valid canonicalKey
    • No user-facing surface depends on the old always-false aborted field (callers read .handled and .stoppedSubagents only)
    • killSessionRun never throws (internal try/catch around process.kill)
    • Zero new @ts-expect-error / @ts-ignore / as any introduced

Test plan

  • pnpm check exit 0
  • pnpm test full suite green (7010 / 800 files)
  • Rescans clean
  • CI green on all required checks

Context

Closes #2343

Refs: #2089, #2344, #2460, #2461

🤖 Generated with Claude Code

…ctually terminate CLI subprocesses (#2343)

Replace three dead `const aborted = false` / `const ended = true`
placeholders left behind by the Pi-embedded gut (`b27cecc795`, #76/#77)
with real calls to `killSessionRun` / `waitForSessionRunEnd` from
`src/agents/session-run-registry.ts`:

- `abort.ts::stopSubagentsForRequester` inner loop — `killSessionRun(childKey)`
  now flows into the `killed` aggregate, so the loop actually counts
  subagent runs whose subprocesses were signaled.
- `abort.ts::tryFastAbortFromMessage` — `killSessionRun(resolvedTargetKey)`
  returned in the `aborted` field (previously always `false`). User-facing
  `/abort` now terminates the running CLI subprocess for non-ACP sessions;
  ACP cancellation path remains separate.
- `sessions.ts::ensureSessionRuntimeCleanup` — replaces the `const ended = true`
  placeholder with `killSessionRun(canonicalKey)` + `waitForSessionRunEnd`
  (15s timeout). Returns `UNAVAILABLE` on timeout, restoring the pre-gut
  contract that `sessions.reset` / `sessions.delete` surface the error
  when a subprocess refuses to end.

Also removes the `if (!params.sessionId) return undefined;` early-exit
in `ensureSessionRuntimeCleanup`: `sessionId` is used only to extend the
queue-key set; termination operates on `canonicalKey`, which is always
present. Callers with no `sessionId` now correctly attempt termination
(`waitForSessionRunEnd` short-circuits to `true` when no run is
registered, so the wait has no cost when there's nothing to wait for).

Polish: extracted `15_000` to `SESSION_RUN_TERMINATION_TIMEOUT_MS`,
grouped with the sibling `ACP_RUNTIME_CLEANUP_TIMEOUT_MS` constant.

Issue body lists three regression sites; only two apply. The third
(`commands-session.ts::applyAbortTarget`) does not exist in the current
tree — `git grep applyAbortTarget` returns zero hits. The `/session
abort` pathway was consolidated into `tryFastAbortFromMessage` at some
earlier point, so no duplicate wiring is needed. AC3 marked N/A.

Verification:
- `pnpm check` → exit 0
- `pnpm test` (full parallel suite) → 800 files / 7010 passed /
  3 skipped — no regression
- Rescan: `git grep "const aborted = false"` → zero hits across the
  whole tree (combined sweep of #2344 subagent kill/steer + #2343
  abort/subagent-cascade/fastAbort)
- Rescan: `git grep "abortEmbeddedPiRun"` → only test-mock contexts;
  production has no remaining references (test-mock cleanup tracked
  under #2089 sweep)
- Adversarial validation (fresh-context subclaude): CLEAN on 7 AC +
  9 adversarial checks — confirms waitForSessionRunEnd is LIVE (50ms
  poll tick, max 15s wall-clock), `ensureSessionRuntimeCleanup` callers
  always pass a valid `canonicalKey`, and no test asserts `aborted ===
  false` on the fast-abort path

Closes #2343

Refs: #2089, #2344, #2460, #2461

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@alexey-pelykh alexey-pelykh merged commit 7784ae7 into main Apr 22, 2026
15 checks passed
@alexey-pelykh alexey-pelykh deleted the fix/2343-abort-session-run-kill branch April 22, 2026 11:26
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.

fix(abort): wire /abort command and sessions.reset/delete to session-run-registry killSessionRun

1 participant