`/stop <id>` to terminate a single background terminal
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:
codex-protocol— add anOp::StopBackgroundTerminal { process_id: String }variant next toCleanBackgroundTerminals, keeping the existing stop-all wire format byte-stable.codex-core— addUnifiedExecProcessManager::terminate_process(i32) -> bool, a session wrapper, a submission-loop handler, and a dispatch arm that emitsEventMsg::Errorwhen the id is unknown or cannot be parsed.codex-tui(+codex-app-serverwiring) — optStopintosupports_inline_args, render/psbullets with a short id, add astop_one_background_terminalhelper with prefix matching, route it through the existingAppCommandView/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
/psshows is what/stopaccepts; matchesExecCommandBeginEvent.process_idso 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 1depends on the most recent/psoutput, 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:
- Trim the arg; empty → existing stop-all path.
- Collect candidates where
process.key.starts_with(arg). - Exactly one match → stop that one.
- Zero matches → error ("No background terminal matching
{arg}. Run/psto see ids."). - Multiple matches → error ("Ambiguous id
{arg}: matches N terminals, be more specific.").
Open questions I'd like input on before any code lands
- 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?
- 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.
- app-server JSON-RPC surface. The TUI routes ops through
thread/backgroundTerminals/clean, so adding/stop <id>end-to-end needs either (a) a newthread/backgroundTerminals/stop { threadId, processId }method, or (b) extendingcleanwith an optionalprocessId. Separate method feels cleaner and keepscleanwire-stable, but I'd like the team's call on this. /clean <id>alias./cleanis currently a silent no-op on inline args. IfStopopts into inline args,/clean a1b2c3would start working automatically. This is almost certainly fine but worth flagging so it's not a surprise.- 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
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗