Skip to content

v2: shell: no way to stop a running background command #36424

Open

Description

@Ploppy3

Summary

The handle-at-launch half is fixed: since v2.0.0 (#45461), background shell responses include the shell ID and output file at launch. Still missing: a model-callable way to stop a running background command using that ID.

Current state (v2 >= 2.0.0)

Tracking: #34366.

Activity

  1. github-actions commented on Jul 11, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. Klark61 commented on Jul 13, 2026

    @Klark61

    Thanks for tracking this — I hit exactly this on Windows (dev servers that can't be stopped without pkill/taskkill by name), and I'd like to propose a concrete shape that also addresses #34366.

    Prior art

    • Claude Code backgrounds a running Bash call (Ctrl+B), returns a task ID, writes output to a file the agent reads later, and auto-terminates on session exit or >5 GB output (disable via CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1).
    • Cline executes commands directly in the terminal, watches output in real time, and reacts to new output as it appears (with cancellation via the SDK's abort signal).

    Both prove the model: the agent launches, gets a handle, observes, and kills — instead of blocking until exit or the 60s timeout.

    Proposed API surface

    Three pieces, all session-scoped (jobs are not cross-session addressable, mirroring Claude Code):

    1. bash gains async?: boolean. When set, the tool returns immediately with { job_id, pid, started_at } instead of blocking. Output is captured into a 1 MiB in-memory ring buffer with file spill (reusing the existing truncation path). The synchronous path is unchanged when async is omitted.

    2. bash_status({ job_id, tail_bytes? }) → { state: "running"|"exited"|"killed", elapsed_ms, exit_code?, output_so_far }. Non-blocking poll. The agent calls it in a loop, reasoning between polls (wait / kill / proceed). Suggested throttling: min 500 ms, max 5 s, backoff when output is unchanged across two polls.

    3. bash_kill({ job_id, signal? }) → { killed, pid }.

    A session-scope finalizer kills any still-running jobs when the session ends, so an agent that forgets to call bash_kill doesn't leak processes.

    How this lands on the existing issues

    Smallest viable first PR

    Ship async:true + bash_status first; bash_kill as a follow-up. Before bash_kill lands, the agent can still kill via a normal synchronous bash call using the pid returned by the async launch (taskkill /PID <pid> /T /F on Windows). This keeps PR 1 focused on the observation problem (the harder half) and lets the registry/ring design bake before committing kill semantics.

    I've verified the current shell.ts on dev — the spawner.spawn / Stream.decodeText(handle.all) / handle.kill({ forceKillAfter }) primitives are exactly what async mode needs, so the change is a fork inside run() plus a new JobRegistry, not a rewrite.

    Happy to open a PR against dev if there's interest. If BackgroundJob (referenced in #34366 as already existing for subagent background mode) is the intended shared primitive, I'm glad to build on top of it instead of a standalone registry — just point me at its interface.

  3. changed the title [-]v2: shell: no way to stop or cancel a running background command[/-] [+]v2: shell: no handle at launch to stop/cancel running background commands[/+] on Jul 23, 2026
  4. Ploppy3 commented on Jul 23, 2026

    @Ploppy3
    Author

    Updating with new v2 findings: background shell commands do return a call ID (call_00_GHYwLiUePO5p7d23ATlI7431) — but only in the completion notification, not at launch. This confirms the infrastructure tracks them; the fix would be to expose the ID at launch time, which would then enable a cancel/kill mechanism. Keeping this open to avoid stale.

  5. changed the title [-]v2: shell: no handle at launch to stop/cancel running background commands[/-] [+]v2: shell: no way to stop a running background command[/+] on Sep 14, 2026
  6. Ploppy3 commented on Sep 14, 2026

    @Ploppy3
    Author

    Updated the issue to reflect the current state: the launch gap is fixed — since v2.0.0 (#45461), background shell responses include the shell ID and output file. The open gap is now just model-facing cancellation (human-only via TUI Ctrl+D today). #48352 adds shell_stop and closes this issue; #46721 adds typed stop outcomes.

  7. Ploppy3 commented on Oct 5, 2026

    @Ploppy3
    Author

    Still valid on v2.0.23 (v2 tip fad352c): the shell tool still exposes only command/workdir/timeout/background — no stop/cancel parameter and no shell_stop tool. Stop remains human-only via shell.remove (TUI Ctrl+D). PR #48352 is still unmerged.

  8. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the clear write-up! You're asking for a model-callable way to stop a running background shell command, using the shellID it gets back at launch, so agents can clean up servers or watchers they started without asking the user to press Ctrl+D.

    I checked the current code: the shell tool has no stop parameter and there's no separate stop tool, so stopping is still only possible from the client side (shell.remove). I'm marking this as a feature request for the maintainers to consider.

    Related:

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions