`/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
4 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Third-party app-server clients (IDE extensions, custom UIs, feature-complete
alternative TUIs like CodexMonitor) hit the same gap at a different layer.
The TUI exposes
/stop→Op::CleanBackgroundTerminals(thread-wide); theapp-server-protocol side exposes
thread/backgroundTerminals/clean(alsothread-wide), but no per-process variant. Clients rendering a live list of
running background terminals — one per
CommandExecutionwithsource: "unifiedExecStartup"stillinProgress— want a per-row Stopbutton and currently have no primitive to bind it to.
Since this issue already maps the core-side plumbing (the
ProcessStoreentry keyed on the i32
process_id, needing a public wrapper that alsoterminates the process handle and unregisters its network approval), a
sibling
thread/backgroundTerminals/killOne { thread_id, process_id }RPC on the app-server would surface the same capability to non-TUI
clients off the same helper. The existing
cleanRPC is already marked#[experimental], so the surface is presumably still mutable.I hit a related Linux/bwrap case that makes this feature more important than just convenience.
Setup:
sandbox_mode = "workspace-write"unified_exec = true--unshare-pidObserved behavior:
npm run dev, viaexec_command.session_idand useswrite_stdinto send Ctrl-C.exec_commandcannot manage it with normal Unix tooling:pgrepfrom the later command does not see the earlier process, because it is in a different bwrap PID namespace.kill <pid>from the later command also cannot target it.pgrepis approved to run outside the sandbox whilekillis still sandboxed, the situation becomes actively misleading:pgrepcan show host PIDs thatkillthen reports asNo such process.So
/stop <id>or an equivalent first-class terminate-by-Codex-process-id action would solve a real operational gap: the agent needs a sandbox-aware way to stop a process it started, without relying on OS PIDs visible from the currentexec_command.This also suggests
/psshould expose the Codex/unified-exec process id clearly, and docs/UI should distinguish that from host OS PIDs.This feature request hasn't received enough upvotes, so it is being closed automatically. If you still feel this feature is needed, please open a new feature request with an updated title and description.