`/stop <id>` to terminate a single background terminal

Resolved 💬 4 comments Opened Apr 14, 2026 by no-on3 Closed Jul 1, 2026

What variant of Codex are you using?

CLI

What feature would you like to see?

Today the TUI exposes two slash commands for background terminals:

  • /ps — list running background terminals
  • /stop (alias /clean) — terminate all running background terminals

There is no way to stop just one. If I have three long-lived shells running (e.g. a dev server, a log tail, a file watcher) and one of them is misbehaving, my only option is /stop, which kills everything, and then re-launching the ones I still wanted. This is friction in an otherwise pleasant workflow.

I'd like /stop to accept an optional id argument, e.g. /stop a1b2c3, that terminates exactly one background terminal. Bare /stop (and /clean) should keep working exactly as they do today — nothing regresses for existing users.

Proposed user-facing behavior:

  • /ps — same output as today, but each bullet gains a short id prefix:

```
Background terminals

• [a1b2c3] bash -lc "npm run dev"
↳ ...
• [d4e5f6] tail -f /var/log/app.log
```

  • /stop — unchanged. Stops everything.
  • /stop <id> — looks up the process by id (or prefix) and stops just that one.
  • /stop <unknown-id> — friendly error, no side effects.
  • /stop <ambiguous-prefix> — ambiguity error listing how many match, no side effects.

---

Additional information

Why this is probably low-cost for Codex

Most of the plumbing already exists. The backend already assigns a random i32 process id per unified-exec session; that id is already propagated to the TUI through ExecCommandBeginEvent.process_id and stored on each UnifiedExecProcessSummary as key; the id is simply not shown in /ps and not accepted by /stop. The ProcessStore even already has a remove(i32) method — the only missing piece on the core side is a public async wrapper that also terminates the process handle and unregisters its network approval (one function, ~12 lines, mirroring terminate_all_processes).

So the scope splits cleanly along three surfaces:

  1. codex-protocol — add an Op::StopBackgroundTerminal { process_id: String } variant next to CleanBackgroundTerminals, keeping the existing stop-all wire format byte-stable.
  2. codex-core — add UnifiedExecProcessManager::terminate_process(i32) -> bool, a session wrapper, a submission-loop handler, and a dispatch arm that emits EventMsg::Error when the id is unknown or cannot be parsed.
  3. codex-tui (+ codex-app-server wiring) — opt Stop into supports_inline_args, render /ps bullets with a short id, add a stop_one_background_terminal helper with prefix matching, route it through the existing AppCommandView / thread/backgroundTerminals/* surface.
Design discussion — the one real judgment call

The backend id is a random i32, currently generated in [1000, 100000). Two options for what to expose to the user:

Option A — raw id as a string, rendered as a 6-char prefix in /ps, prefix-matched on /stop <arg>.

  • Pro: stateless; what /ps shows is what /stop accepts; matches ExecCommandBeginEvent.process_id so logs/rollouts line up.
  • Pro: users can paste the full id from logs, or type just enough of a prefix to disambiguate.
  • Con: the visible id is not "pretty" — it's just a prefix of a decimal number.

Option B — stable short aliases (1, 2, …) rendered at /ps time and remembered on ChatWidget until the next /ps invocation.

  • Pro: very friendly to type.
  • Con: aliases drift as processes start/stop, so /stop 1 depends on the most recent /ps output, which is subtle and error-prone.
  • Con: new mutable TUI state; invalidated by each subsequent /ps.

I'd pick A. Short aliases feel ergonomic in isolation but the "last /ps wins" semantics are a trap once the user has two terminal sessions open or scrolls back in the transcript.

Matching rules for /stop <arg> that I'd suggest:

  1. Trim the arg; empty → existing stop-all path.
  2. Collect candidates where process.key.starts_with(arg).
  3. Exactly one match → stop that one.
  4. Zero matches → error ("No background terminal matching {arg}. Run /ps to see ids.").
  5. Multiple matches → error ("Ambiguous id {arg}: matches N terminals, be more specific.").
Open questions I'd like input on before any code lands
  1. Id format. Option A vs B above, or something else (e.g. a short hex slug instead of the raw decimal prefix). Is there a house style here?
  2. Ambiguity handling. Should a 2-match prefix error out (my suggestion), or deterministically stop the first one and warn? I chose the strict option because silent "pick one" behavior is surprising.
  3. app-server JSON-RPC surface. The TUI routes ops through thread/backgroundTerminals/clean, so adding /stop <id> end-to-end needs either (a) a new thread/backgroundTerminals/stop { threadId, processId } method, or (b) extending clean with an optional processId. Separate method feels cleaner and keeps clean wire-stable, but I'd like the team's call on this.
  4. /clean <id> alias. /clean is currently a silent no-op on inline args. If Stop opts into inline args, /clean a1b2c3 would start working automatically. This is almost certainly fine but worth flagging so it's not a surprise.
  5. No JSON-RPC surface, TUI-only? If the team would rather scope this to the TUI only (no new app-server method), I'd need to know because TUI ops round-trip through the app-server in production, so "TUI-only" would require a different plumbing strategy.
Reference implementation

I have a working implementation on my fork, split across three atomic commits (core, app-server, tui) with unit tests, an integration test that spawns two real processes and verifies OS-level termination of one and survival of the other, and regenerated insta snapshots for /ps. It's available here if it would be useful as a starting point for design discussion or as a reference once the direction is agreed:
https://github.com/no-on3/codex/pull/1

I put this together primarily to validate that the design holds up end-to-end, and I'm happy to iterate on the design in this thread before any of that gets looked at.
Thanks

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗