---
title: Moving a session between projects
---

# Move a session between projects

## What it is

### What it is

**Move a session** relocates one or more Claude Code sessions from the project they currently live under to a different project in your sidebar. It is a quick organizational action — you reach it from the session's menu (the right-click menu on a sidebar row, the `⋯` menu at the top of an open session, or the right-click menu on a session row in the **Inbox**) **or the `M` keyboard shortcut**, pick the destination project, and the session immediately re-homes under it. In the left sidebar **and in the Inbox** you can **multi-select several sessions and move them all at once**; a single **Undo** toast lets you put it (or all of them) back exactly where it was.

The feature exists because sessions sometimes land in the wrong place — you started a session in "Scratch" that turned into real work for "Website", or you cloned a session's intent into the wrong project. Rather than archiving and re-spawning, you just move the existing session, conversation history and all.

The one subtle thing it does is **protect where a started session runs**. A session that has already spawned a Claude CLI process keeps running against its **original folder** even after the move — the move changes which project the session is _filed under_ in the sidebar, but it does NOT yank a live agent's working directory out from under it (that would break in-flight file paths and git context). A session that has never been spawned yet adopts the destination project's folder, because nothing is pinned to the old one. See "What happens to the working directory" below.

## Where to find it

A session's **right-click / ⋯ menu → Move**, or the **M** key with a session selected in the sidebar or Inbox.

## How it behaves

### How to use it

> **Keyboard shortcut:** with a session selected in the sidebar/Inbox (or open in front of you), press **`M`** to open the destination picker directly — the same picker, skipping the menu. If you have **several sessions multi-selected**, `M` moves them all, exactly like the menu. It does nothing when no session is in context, and (like the other single-letter session keys) it stays out of your way while you're typing in a reply box. `M` is rebindable in **Settings → Keyboard Shortcuts** and appears in the `?` shortcuts cheat-sheet.

1. **Open the session's menu** from any place it lives:
   - **In the left sidebar** — right-click the session row, or click its `⋯` ("More actions") button.
   - **In an open session** — click the `⋯` menu in the session's header.
   - **In the Inbox** — right-click a session row (the "Move to" item shows only for actual sessions, not SMS / email / alarm / recipe rows). If you have **multi-selected several session rows** in the Inbox, right-clicking one of them moves the **whole selection** and the item reads **"Move N selected to"**.
2. **Click "Move to"** (folder-with-arrow icon) — or just **hover over the row for a moment** and the submenu pops out on its own (standard menu behavior; clicking still works). The eligible destination projects appear under a **"Search projects…"** box — as a side submenu from the sidebar or Inbox menu, or expanding **inline** in the header `⋯` menu.
3. **Type to filter** if you have many projects, then **click the destination project**. The submenu and menu close.
4. The session **immediately moves** — it disappears from the old project's session list and appears under the new one. A toast reads **`Moved "<session>" to <project>`** with an **Undo** button.
5. **Undo** (click the toast button, or press your Undo-last-action shortcut — Ctrl+Z by default) puts the session back under its original project in its original position.

### Which projects can you move to

The destination list only shows **eligible** projects — the ones that can actually host a running session. A project is excluded when it is:

- **the project the session is already in** (you can't move it to itself),
- a **soft-deleted** project, or
- a **virtual / built-in area with no real session folder** — the sidebar entries whose folder path is a sentinel like Gmail, SMS, Scratchpads, Tasks, Stats, Settings, etc. These have no folder on disk, so a session can't run under them.

**Claude is an eligible destination.** The built-in **Claude** area is the one sentinel that is _not_ excluded: unlike the inbox/tool sentinels above, it resolves to a real folder (`~/Claude`) and hosts Claude sessions natively, so you can move a session into it like any ordinary project. (Internally the rule is "a real project folder **or** Claude" — not the older blunt "anything whose folder path starts with `__` is out", which wrongly hid Claude.)

If no projects qualify, the submenu shows **"No projects"**.

### Where the move shows up

A move re-homes the session **live on every surface at once** — not just the window you did it in. The window you moved it from updates instantly, and the change also propagates to your **other open windows** and your **phone / web session**: the sidebar session list and the per-project counts re-home the session there on their own, with no manual refresh. A move made by **an agent or a script over the CLI control API** (`POST /session/:id/move`, below) shows up the same way — the app broadcasts a "session moved" signal that every open client applies, so an agent tidying your projects is reflected on your screen immediately. The one thing that is not instant on a device that did not initiate the move is the session's exact **position within** the destination project's list; that settles on the next normal refresh (the re-home itself is immediate).

### What happens to the working directory

This is the only non-obvious behavior, and it is deliberate:

- **A session that has already been started** (it has spawned at least one Claude CLI process) keeps its **original working directory permanently**. The move stamps that folder onto the session as a pin so that resuming or sending another message still runs the agent in the same place it has always run — its file edits, git history, and relative paths stay valid. Moving it again later does **not** repoint it; the first pin wins.
- **A session that has never been started** (a fresh, unspawned row) has no pin, so after the move it resolves to the **destination project's folder**. When you eventually send its first message, the agent spawns in the new project's directory.

In short: moving is safe for running work (it never changes where a live agent operates) and intuitive for brand-new sessions (they follow the project you filed them under).

### Undo is exact and backend-owned

The Undo restores the session's previous **project, sidebar position, working-directory pin, and last-active timestamp** from a record the main process kept at move time — not from anything the UI guessed. So Undo returns the session to precisely the state it was in before the move, including its old spot in the list order. The undo record is authoritative on the backend, which means the restore is correct even if the sidebar re-sorted in the meantime.

### Scope and limits

- **Also drivable over the CLI control server.** Besides the in-app menus and the `M` hotkey, a move is exposed as **`POST /session/:id/move`** on Omniscio's local control server (it calls the same shared move core as the menu — `moveSessionToProjectService` — so every guarantee here holds identically, including the working-directory pin and the exact undo). So an external script or an AI driving Omniscio over the CLI control API **can** move a session, and — exactly like an in-app move — it re-homes the session **live on every open client** (see "Where the move shows up" above), not just on the device that made the call.
- **Bulk from the sidebar or the Inbox; single from an open session's header.** When you **multi-select** session rows — in the left sidebar (Shift/Ctrl-click, or Shift+J/K) **or in the Inbox** — and right-click one that's part of the selection, **"Move to"** moves **every selected session** in one action: the item's label reads **"Move N selected to"**, the toast reads **`Moved <N> sessions to <project>`**, and a single **Undo** reverts them all, each back to its own original project (they can even come from different source folders — an Inbox selection commonly spans projects, and they all consolidate into the one you pick). The Inbox scopes the batch to what you've selected **in the Inbox** — a leftover sidebar selection never bleeds into an Inbox move. The open-session header `⋯` menu still acts on a single session, because it only ever has one session in context. Under the hood the bulk path just calls the same per-session move once per selected id (no separate bulk backend), so every safety guarantee below holds identically for each session.
- **Conversation history travels with the session.** Moving re-files the whole session record — its messages, status, and tags are unaffected; only the parent project (and, for unspawned sessions, the eventual spawn folder) changes.

## For agents

### Where things live (for agents with repo access)

- Shared move gesture + Undo toast: [/src/renderer/src/features/dashboard/useMoveSession.ts](/src/renderer/src/features/dashboard/useMoveSession.ts) — `useMoveSession` (single) calls the store's `moveSessionToProject(sessionId, targetProjectId)`, then registers an Undo that calls `undoMoveSession(...)` and shows the `sessionActions` toast. `useBulkMoveSessions` (the sidebar multi-select **and the Inbox multi-select**) calls the store's `bulkMoveSessionsToProject(ids, targetProjectId)` (optimistic + per-id reconcile, looping the same single IPC), then arms ONE Undo that loops `undoMoveSession(id, priorProjectId)` per moved session. For the Inbox, [/src/renderer/src/features/dashboard/SidebarOverlays.tsx](/src/renderer/src/features/dashboard/SidebarOverlays.tsx) resolves which sessions a right-click moves via `resolveInboxSessionsToMove` ([/src/renderer/src/features/dashboard/inbox-helpers.ts](/src/renderer/src/features/dashboard/inbox-helpers.ts)) over the Inbox-scoped selection from `getSelectedInboxSessionIds` ([/src/renderer/src/lib/bulk-inbox-actions.ts](/src/renderer/src/lib/bulk-inbox-actions.ts)) — never the sidebar union — and routes >1 target to `useBulkMoveSessions`, else the single `useMoveSession`.
- Destination picker (eligibility filter + search): [/src/renderer/src/features/dashboard/MoveToProjectMenu.tsx](/src/renderer/src/features/dashboard/MoveToProjectMenu.tsx).
- Menu entry points: the **"Move to"** item in [/src/renderer/src/features/dashboard/SessionContextMenu.tsx](/src/renderer/src/features/dashboard/SessionContextMenu.tsx) (left-sidebar right-click / kebab — a side flyout), [/src/renderer/src/features/sessions/SessionOverflowMenu.tsx](/src/renderer/src/features/sessions/SessionOverflowMenu.tsx) (open-session header `⋯` menu — an inline picker), and [/src/renderer/src/features/dashboard/InboxItemContextMenu.tsx](/src/renderer/src/features/dashboard/InboxItemContextMenu.tsx) (Inbox row right-click — a side flyout, gated to session rows via `findMovableSessionForInboxItem` in [/src/renderer/src/features/dashboard/inbox-helpers.ts](/src/renderer/src/features/dashboard/inbox-helpers.ts)).
- Keyboard shortcut (`M`): the `moveSession` action is declared in [/src/shared/keybindings/core.keybindings.ts](/src/shared/keybindings/core.keybindings.ts) and handled by `handleMoveSession` in [/src/renderer/src/hooks/keyboard-shortcuts/session-move-action.ts](/src/renderer/src/hooks/keyboard-shortcuts/session-move-action.ts), which sets a target on [/src/renderer/src/features/dashboard/move-session-picker-store.ts](/src/renderer/src/features/dashboard/move-session-picker-store.ts); the globally-mounted [/src/renderer/src/features/dashboard/MoveSessionPicker.tsx](/src/renderer/src/features/dashboard/MoveSessionPicker.tsx) opens the same `MoveToProjectMenu` in a dialog and dispatches the same `useMoveSession` / `useBulkMoveSessions` gestures. Each "Move to" menu trigger also carries `hotkeyActionAttr('moveSession')`, so its hover tooltip shows the current key.
- Backend move + undo (project re-home, working-dir pin, exact inverse): `moveSessionToProject` / `undoSessionMove` in [/src/main/db/queries-sessions/lifecycle.ts](/src/main/db/queries-sessions/lifecycle.ts), the pre-move snapshot in [/src/main/services/move-undo-registry.ts](/src/main/services/move-undo-registry.ts), and the workdir pin tier in [/src/main/services/work-dir-resolver.ts](/src/main/services/work-dir-resolver.ts).
- IPC channels: `session:move-to-project` and `session:move-undo` in [/src/main/ipc/session-handlers.ts](/src/main/ipc/session-handlers.ts).
- Live re-home on every surface: the move broadcasts a `SESSION_MOVED` push (`session-move-service.ts`, the CLI route, and undo) that the renderer consumes in [/src/renderer/src/hooks/session-sync/useSessionMetadataSync.ts](/src/renderer/src/hooks/session-sync/useSessionMetadataSync.ts) (`handleSessionMoved` → the `updateSessionProjectId` store reducer, find-anywhere across live/archived/lazy). It is allowlisted for the web/mobile bridge in [/src/main/services/web/web-access-push-filter.ts](/src/main/services/web/web-access-push-filter.ts) so a paired phone re-homes too. Locked by `move-session-contract.md` I8.

## Related

### Related

- [archive-a-session.md](archive-a-session.md) — hide a session (also soft, also undoable) when it's done rather than re-filing it
- [snooze-a-session.md](snooze-a-session.md) — defer a session instead of moving it
- [reorder-projects.md](reorder-projects.md) — rearrange the projects you're moving sessions between
- [add-a-project.md](add-a-project.md) — create the destination project first if it doesn't exist yet

