---
title: Pause or stop a session (four ways, and which one you want)
---

# Pause or stop a session

## What it is

### What it is

Omniscio has four different ways to stop a session from running, and they do different things — pick the one that matches your intent:

- **Pause** freezes the session. The Claude CLI process is killed and the row turns grey with the "Paused" status. Sending a new message (typed reply or Question Widget answer) auto-respawns the CLI with `--resume <cliSessionId>` so multi-turn context is preserved — same auto-resume path as `ended` / `error` / `archived`. The conversation history stays intact. Pause is mutually exclusive with snooze. Pause is the right choice when you want to walk away from the work and explicitly stop the agent from doing anything until you come back; if you want a hard "don't auto-spawn even if I send something", there is no such gate today.
- **Esc / Interrupt** stops only the current turn. If Claude is mid-response and you want it to stop right now (e.g. it's heading down the wrong path), pressing **Esc** with focus on the composer kills the current CLI process exactly like Ctrl+C would in a terminal. The session does NOT pause — its status flips to "Needs You" with the "Stopped" sub-state, a "Session stopped by you" note is recorded in the transcript, and your next Send respawns the CLI and continues the conversation. Esc does not pause and does not archive.
- **Stop while a session is restoring** cancels a comeback you didn't want. After Omniscio restarts it brings the previously-running sessions back a few seconds apart, and each one shows _"Restoring this session from your last app close — this is automatic and may take a moment."_ The red **Stop** button is offered in that window too, and pressing it takes that one session out of the restart queue so it never launches — it settles quietly under **Interrupted**, and every other session still coming back is unaffected. Use it when you spot a session you'd rather not have running. Esc does not do this (it keeps its running-only meaning and just closes the panel), and stopping one session here is not the same as the "Resuming N sessions" toast's Stop, which calls off the whole wave.
- **Stop from the sidebar (right-click)** ends a session **without opening it**. Right-click the session's row (or a multi-selection) in the sidebar and choose **Stop** — it appears whenever a selected session is `running` or `starting`, kills the CLI, and leaves each row grey (`ended`), keeping whatever the agent had already streamed. Unlike Esc it does NOT land in "Needs You" — the row settles as a quiet `ended` you can bring back any time with a message, the "Please continue" Nudge, or right-click → **Restart**. It never targets a live "Needs You" conversation, and it isn't Ctrl+Z-undoable (the only reversal is a paid re-spawn). **It sticks, too:** once you Stop a session this way, nothing brings it back on its own — not the auto-restarter, not another agent's message, not the system that lands finished branches — only your own restart or message does. Same rules as the Manage-sessions modal's Stop view — see [bulk-stop-restart-sessions.md](bulk-stop-restart-sessions.md).
- **End / restart** happens on its own. A session reaches the `ended` state when the agent finishes a turn cleanly (or when you Stop it from the sidebar, above). From `ended`, sending a new message auto-respawns the CLI for the next turn.

**Stop a usage-limit-parked session from its banner.** When a session is parked on a usage limit — the ember "Waiting on a usage limit — resumes automatically" banner — that banner carries a one-click **"Stop"** button. It runs the same **Pause** described above: the session halts, and because a paused session is excluded from the usage-limit auto-resume, it won't restart until you **Unpause** it (from the session's "…" menu). Nothing is archived. This is the per-session way to stop a parked session, whose ordinary running-only Stop button is gone while it waits.

A separate concept, **archive**, takes the session out of the sidebar entirely; see [archive-a-session.md](archive-a-session.md). And **snooze** hides a session until a future time without killing the process — see [snooze-a-session.md](snooze-a-session.md).

The `paused` status sits in `TERMINAL_STATUSES` alongside `ended`, `error`, and `archived`. All four behave identically on Send: the backend's `sendResponse` checks for `!messageId` (no in-flight CLI process) and runs the auto-resume path — `--resume <cliSessionId>` for `paused` / `ended` / `error`, plus auto-unarchive for `archived`. **No status blocks Send — `starting` included.** Answering a question while a session is still coming up is delivered the same way the composer delivers a typed message: the text is folded into the launching turn, or it expedites a session still waiting in the restore queue.

## Where to find it

A session's **⋯ (more actions) menu** in its header carries Pause and Unpause, and the same actions are on the session row and in the bulk-session controls. Each has a keyboard shortcut shown beside it in the menu.

## How it behaves

### How to use it

1. **To pause an active session.** Open the session, click the three-dot menu (`MoreHorizontal`) in the session header, and choose **Pause** (pause icon). The row immediately turns grey and the status flips to "Paused". The default keyboard shortcut is **P** (bound to the `pauseSession` action) — pressing it on the active session toggles between Pause and Unpause. The same Pause action is available in the sidebar's right-click context menu when one or more selected sessions are running, where it labels itself "Pause N sessions" for bulk operations. A single-session **Pause** also appears in the inbox row's right-click menu (the Open / Dismiss / Snooze… menu) — but only when that row is backed by a live session that's currently running; it's hidden for SMS, email, digest, and approval rows, and for sessions that have errored, ended, or are already paused. **Pausing is reversible:** an **Undo** toast appears and **Ctrl+Z** reverts the pause — the session returns to its prior slot in the list with focus restored. (Because pausing kills the agent, undo returns the row to `ended`, not a fake "running" state.)
2. **To unpause.** Open the paused session and click the three-dot menu again — the **Pause** entry has been replaced by **Unpause** (play icon). Picking it flips the status to `ended` (not `running`) — the CLI is not auto-spawned. The session is now ready to accept your next message, which will respawn the CLI on Send. **P** also works on a paused session and toggles to Unpause. Unpausing via the sidebar context menu labels the entry "Unpause N sessions" when you've selected paused rows. Like pause, a single-session unpause is undoable — **Ctrl+Z** (or the **Undo** toast) re-pauses it and returns focus to the session.
3. **To stop Claude mid-stream.** Click anywhere in the composer (or just have it focused) and press **Esc** (or click the red Stop button). The CLI process is killed and the session flips to "Needs You" / "Stopped" **immediately** — it stops thinking the instant you press it, regardless of how busy the machine is. Esc inside the composer also closes the panel if no agent turn is in progress, so it's safe to mash. There's no toast for a successful interrupt — the session row going from green to amber is the feedback. If a Question Widget is open in the chat, Esc focuses that widget instead so you can answer it with letter hotkeys.
4. **To send a new message after a stop / interrupt.** Just type and Send. Omniscio respawns the CLI with `--resume <cliSessionId>` so the agent picks up exactly where it left off, including the conversation context Claude has built up in this session. There's a one-click **"Please continue" Nudge button** for resuming without typing. It appears on any grey session that already has a conversation — `paused` and `ended` sessions (shown regardless of who spoke last, so a finished session still offers it), as well as user-stopped (`needs_you` / "Stopped"), `error`, and `stalled` sessions. On a `paused` session that single click does both steps at once: it unpauses and sends "Please continue" through the same auto-resume path as a normal Send (#5). The button is hidden only when there's no conversation yet — nothing to continue.
5. **Send on a paused session auto-respawns the CLI.** Typing into the composer and clicking Send (or filling in a Question Widget and clicking its Send button) on a paused session triggers the same auto-resume path as Send on `ended` / `error` / `archived` — the CLI is re-spawned with `--resume <cliSessionId>`, the row flips back to `running`, and the agent picks up multi-turn context. If you want the row to _stay_ paused, leave the composer alone. No status rejects Send, `starting` included — a session still coming up takes the text into its launching turn instead; snoozed sessions auto-clear the snooze on Send, and archived sessions auto-unarchive.
6. **Pause works from anywhere via keyboard.** **P** is global — it does not need the composer to be focused. With one or more sessions selected in the sidebar (Shift+Arrow extends a range), **P** issues a bulk pause with an Undo toast: "Paused N sessions" with **Undo** that calls `bulkUnpauseSessions`.
7. **What about a session that's already ended?** Pause is hidden in the overflow menu when status is `paused` or `archived`. For `ended` and `error`, Pause is still shown — it kills nothing (the CLI has already exited) and just flips the row to `paused`, which is mostly useful as a "leave this alone, don't auto-respawn on accidental Send" gate.
8. **What about an archived session? P deposits it into paused.** The overflow Pause entry is hidden for archived sessions (the menu reflects #7), but the **P** keyboard shortcut is _not_ — pressing P on an archived session you currently have open un-archives it INTO the paused bucket atomically, then applies the usual pause behaviour (sidebar row reappears, the slice migrates from `archivedSessions[]` to `sessions[]`). Bulk **P** with a mixed selection (some archived, some active) runs both arms in parallel and clears selection on settle. This is invariant **`keybindings-deposit-across-the-archive-boundary`** in [archive-session-contract.md](../../.claude/memory/contracts/archive-session-contract.md). Before 2026-05-23 the keyboard path was a silent no-op on archived rows because the dispatcher only looked in `sessions[]`.
9. **What about a session that's still streaming?** Pause from the overflow menu while streaming first calls `processManager.terminate(sessionId)` to kill the CLI, then writes `paused` to the session row. The current turn is cut off — anything Claude was about to say but hadn't said yet is lost. If you want the turn to finish but the session frozen afterwards, wait for `ended` before pausing.
10. **What about a session that's still restoring after an app restart?** Press the same red **Stop** button — it is shown while the "Restoring this session from your last app close…" line is up, not only once the session is running. There is no CLI process to kill yet (the session is waiting its turn in the restart queue), so Stop takes it out of that queue instead and settles it under **Interrupted**, with a note in the transcript explaining why it stopped. The rest of the wave keeps coming back — this stops only the session you pressed it on. Bring it back whenever you like with a message, the **"Please continue"** Nudge, or right-click → **Restart**. If the queue happened to launch the session in the same instant you pressed Stop, the press lands as a normal mid-turn interrupt instead, which is the same outcome by a different route. To stop *everything* still coming back, use the **Stop** button on the "Resuming N sessions" toast; to pick several, use the **Queued** view in Manage Sessions.
11. **What about a Codex, Pi, Gemini, Kimi/Hermes or OpenCode session whose engine is still starting?** Stopping, pausing or archiving it then ends the start too: its engine is shut down rather than left running in the background, the message you had just sent is never delivered to it, and the session does not show a "Failed to start" error for it. Before 2026-09-25 that engine could keep running, still working on the message, until Omniscio quit.

## For agents

### How it works

The Pause/Unpause menu items live in [SessionOverflowMenu.tsx](../../src/renderer/src/features/sessions/SessionOverflowMenu.tsx) — `showPause` is `session.status !== 'paused' && session.status !== 'archived'`, `showUnpause` is `session.status === 'paused'`. The handlers call `useSessionStore.getState().pauseSession()` / `unpauseSession()` on the renderer's [session-store.ts](../../src/renderer/src/stores/session-store.ts), which invoke `IPC.SESSION_PAUSE` / `IPC.SESSION_UNPAUSE`. The IPC handlers in [session-handlers.ts](../../src/main/ipc/session-handlers.ts) delegate to `pauseSessionService()` / `unpauseSessionService()` in [session-lifecycle-service.ts](../../src/main/services/session/session-lifecycle-service.ts) — that's the shared business logic for both renderer-driven pauses and CLI-pending dispatcher pauses, so identical DB writes and identical push events fire from either entry point.

`pauseSessionService` is idempotent: already-paused returns success without side effects, archived returns an error. If the session has a running CLI process it calls `processManager.terminate(sessionId)` first, then `queries.pauseSession(id)` writes `status='paused', snoozed_until=NULL` to SQLite (excluding archived/already-paused rows in the WHERE clause), and emits `SESSION_STATUS_CHANGED` with `status: 'paused', snoozedUntil: null, pendingAction: null`. Unpause flips status back to `'ended'` (not `running` — the CLI doesn't auto-spawn). The keyboard binding is in [keybindings.ts](../../src/shared/keybindings.ts) (`pauseSession`, default `P`); the dispatcher is in [useKeyboardShortcuts.ts](../../src/renderer/src/hooks/useKeyboardShortcuts.ts) `case 'pauseSession'` and handles both single-session toggle and bulk pause/unpause when the sidebar selection is non-empty. Every pause/unpause surface (keyboard, overflow menu, context menu, bulk) registers its undo through the shared `registerSessionActionUndo` chokepoint in [session-action-undo.ts](../../src/renderer/src/stores/session-action-undo.ts) — that single call both registers the undo and shows the Undo toast, so Ctrl+Z and the toast's Undo button run the same reversal. The undo runs the raw inverse store method (`unpauseSession` for a pause, `pauseSession` for an unpause) and restores focus to the acted-on session. The full rule set is locked in [session-action-undo-contract.md](../../.claude/memory/contracts/session-action-undo-contract.md).

Esc / interrupt is a different IPC: `IPC.SESSION_INTERRUPT` in [process-control.ts](../../src/main/ipc/session/process-control.ts) calls `processManager.interrupt(sessionId)`, which stops the session **synchronously** — it cancels any armed deferred-work wakeup, kills the child AND bumps the spawn epoch (so the dying CLI's late output becomes an immediate no-op), finalizes the partial answer, and flips the row to "Needs You / Stopped" right then. The non-Claude engines route the same IPC through `interruptSessionService`, which hands a Codex or Pi session to that engine's own `interrupt()` — as of 2026-07 those land in the same visible, re-sendable **"Needs You"** state, with the "Session stopped by you" note in the transcript, via the shared `concludeInterruptAwaitingUser` — instead of the grey `ended` row in the "Interrupted" bucket they used to drop into silently. (The external engines don't PERSIST a `pendingAction`, so they show a plain "Needs You" dot rather than Claude's "Stopped" sub-label; the transcript note carries the "you stopped this" signal.) It does NOT leave the status on "running" waiting for the OS to reap the process — that deferral was the old "Stop keeps thinking for a minute under load" bug. The invariants are locked in [instant-stop-interrupt-contract.md](../../.claude/memory/contracts/instant-stop-interrupt-contract.md). The composer's Esc handler is in [useSessionPanel.ts](../../src/renderer/src/features/sessions/useSessionPanel.ts) (`if (session.status === 'running') handleInterrupt() else onClose()`). Auto-resume on the next Send is handled by `processManager.writeToStdin()` re-spawning with `--resume <cliSessionId>`. See [process-management.md](../../.claude/memory/process-management.md) for the full lifecycle and the stale-account / rate-limit recovery paths that interact with pause.

A stop, pause or archive that lands while a Codex, Pi, Gemini, Kimi/Hermes or OpenCode engine is still starting is handled by that engine's own manager: every engine start runs through `runOwnedStart` in [base-external-session-manager.ts](../../src/main/services/base-external-session-manager.ts), which keeps one start per session and, once the start settles, stops the engine if the session was closed, is closing (every `terminate()` opens with `beginClose`) or was re-opened meanwhile — without delivering the pending message or writing to the session. Locked by `stop-during-start-ends-the-start` in [session-stop-kill-contract.md](../../.claude/memory/contracts/session-stop-kill-contract.md).

## Related

### Related

- [bulk-stop-restart-sessions.md](bulk-stop-restart-sessions.md) — the sidebar right-click **Stop** / **Restart** and the Manage-sessions modal for acting on many sessions at once
- [archive-a-session.md](archive-a-session.md) — when you want the session out of the sidebar entirely, not just frozen
- [snooze-a-session.md](snooze-a-session.md) — when you want to hide a session for a while but keep it running in the background
- [send-a-message.md](send-a-message.md) — Send semantics for paused / ended / archived sessions, including auto-unsnooze and auto-unarchive
- [session-stuck-in-needs-you.md](session-stuck-in-needs-you.md) — diagnose a session that flipped to "Needs You / Stopped" after Esc and you're not sure why

