# Manage coding sessions

> Create, start, stop, fork, and rewind sessions; teleport/import flows; message queue coalescing; permission and plan approval dialogs; and session switcher UX.

- Repository: Parcha-ai/build
- GitHub: https://github.com/Parcha-ai/build
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b/llms-full.txt

## Source Files

- `src/main/ipc/session.ipc.ts`
- `src/main/services/session.service.ts`
- `src/main/services/message-queue.service.ts`
- `src/renderer/components/session/NewSessionDialog.tsx`
- `src/renderer/components/chat/MessageQueuePanel.tsx`
- `src/renderer/components/chat/ForkTabs.tsx`
- `src/main/ipc/queue.ipc.ts`

---

---
title: "Manage coding sessions"
description: "Create, start, stop, fork, and rewind sessions; teleport/import flows; message queue coalescing; permission and plan approval dialogs; and session switcher UX."
---

Session lifecycle in Build is driven by `SessionService` in the main process, exposed through typed IPC channels (`session:*`) and mirrored in the renderer `session.store`. Creating, activating, forking, rewinding, teleporting, and deleting sessions all persist to the `claudette-sessions` electron-store; chat backpressure uses a main-process `MessageQueueService` that drains into the renderer on `queue:send-next` after each agent turn completes.

## Architecture

```mermaid
flowchart TB
  subgraph renderer["Renderer"]
    NSD[NewSessionDialog]
    SL[SessionList]
    FT[ForkTabs]
    SS[SessionSwitcher]
    SSZ[session.store]
    MQP[MessageQueuePanel]
    PD[PermissionDialog]
    PP[PlanPanel]
  end

  subgraph main["Main process"]
    SIP[session.ipc.ts]
    SVC[SessionService]
    QIPC[queue.ipc.ts]
    MQS[MessageQueueService]
    CIPC[claude.ipc.ts]
  end

  subgraph storage["Persistence"]
    ES[(claudette-sessions)]
    CT[~/.claude/projects/*.jsonl]
  end

  NSD --> SSZ
  SL --> SSZ
  FT --> SSZ
  SS --> SSZ
  MQP --> SSZ
  PD --> SSZ
  PP --> SSZ
  SSZ -->|window.electronAPI.sessions| SIP
  SSZ -->|queue:*| QIPC
  SIP --> SVC
  QIPC --> MQS
  MQS -->|drain-ready| CIPC
  CIPC -->|queue:send-next| SSZ
  SVC --> ES
  SVC --> CT
```

| Layer | Responsibility |
|-------|----------------|
| `session.ipc.ts` | Registers `SESSION_*` handlers; broadcasts `SESSION_STATUS_CHANGED` and `SESSION_LIST_UPDATED` |
| `SessionService` | CRUD, discovery, fork/rewind transcript surgery, SDK `forkSession` for local conversations |
| `message-queue.service.ts` | Per-session FIFO queue, streaming gate, harness-aware drain delay |
| `session.store.ts` | UI state: active session, pending dialogs, local queue display, send/coalesce logic |

## Session statuses

<ParamField body="status" type="SessionStatus">
`creating` | `starting` | `setup` | `running` | `stopping` | `stopped` | `error`
</ParamField>

`startSession` and `stopSession` today flip `running` ↔ `stopped` without starting Docker containers for typical dev sessions. `setActiveSession` auto-invokes `startSession` when you focus a `stopped` session.

## Create sessions

`NewSessionDialog` is the primary entry for new work. It walks a multi-step wizard (`source` → repo/folder/config/teleport/ssh/openclaw) and calls different IPC surfaces depending on source.

<Steps>
<Step title="Pick a source">
GitHub repo (clone via `sessions.create`), local folder (`dev.createSession`), SSH (`ssh.createSession`), OpenClaw gateway (`openclaw.createSession`), or **Import from claude.ai** (teleport step).
</Step>
<Step title="Configure workspace">
For local folders: optional git worktree with setup script or instructions saved under `.grep/`. Branch picker loads when `dev.checkGitRepo` reports a repo.
</Step>
<Step title="Activate">
On success, `addSession` + `setActiveSession` run; setup progress streams via `dev.onSetupProgress` / `ssh.onSetupProgress`.
</Step>
</Steps>

| Source | IPC | Notes |
|--------|-----|-------|
| GitHub | `SESSION_CREATE` → `SessionService.createSession` | Clones into `userData/sessions/{id}/`, writes `.grep/setup.sh`, ends `stopped` |
| Local folder | `dev.createSession` | `isDevMode: true`; optional worktree under `~/.claudette/worktrees/` |
| SSH | `ssh.createSession` | `sshConfig` on session; may `resumeSessionId` |
| OpenClaw | `openclaw.createSession` | `openclawConfig` gateway URL + password |
| claude.ai import | `dev.createTeleportSession` | Requires Claude CLI; spawns with `--teleport` into chosen `cwd` |

<Note>
`NewSessionDialog` checks Claude CLI via `dev.checkClaudeCli` on open. Teleport/import and some resume paths depend on it being installed.
</Note>

## Start, stop, and delete

| Action | IPC channel | Behavior |
|--------|-------------|----------|
| Start | `session:start` | Sets status `running` |
| Stop | `session:stop` | Sets status `stopped` |
| Delete | `session:delete` | Stops Docker container if present; `claudeService.cleanupSession` + `browserService.cleanupSession`; purges renderer maps and `messageQueueService.cleanup` |

Deletion also clears `secureKeys` for the session and removes browser/plan/TTS side effects from other stores.

## Conversation forks

Build distinguishes **git worktree forks** (`isWorktree`, `parentRepoPath`, `forkName`) from **conversation forks** (`parentSessionId`, `childSessionIds`, `forkPoint`, `aiGeneratedName`).

### Create fork from chat

Sending while holding a fork intent (or explicit fork UI) calls `SESSION_CREATE_FORK`:

- **Local**: uses Claude Agent SDK `forkSession(parentSdkSessionId, { upToMessageId?, title, dir? })`.
- **SSH**: allocates a new UUID; first query resumes parent with `forkSession: true` server-side.
- Parent gains the child in `childSessionIds`; `sdkSessionMappings` wires transcript lookup.
- `createForkFromCurrent` in the store creates at `'end'`, switches to the fork, and sends the user message only to the child (parent stream continues).

### Fork tabs UI

`ForkTabs` renders siblings from `getForkSiblings` / `SESSION_GET_FORK_GROUP` (root + descendants sorted by `forkCreatedAt` or `createdAt`). Closing a tab sets `tabHidden` and moves the session to an overflow menu (`grep-overflow-forks-{rootId}` in `localStorage`). SSH roots auto-call `SESSION_SCAN_REMOTE` once to register orphan remote transcripts as hidden child sessions.

## Rewind and fork

Two rewind paths exist:

1. **Transcript rewind-fork** (`SESSION_REWIND_FORK` / `rewindAndFork`): Truncates `~/.claude/projects/{hash}/{sessionId}.jsonl` at a message UUID, writes a new `{forkedId}.jsonl`, clones Build supplemental transcript via `transcriptService`, creates a new session record, and switches the UI (`MessageBubble` rewind button on non-latest user messages).

2. **File rewind + fork** (`CLAUDE_REWIND_EXECUTE`): SDK `rewindFiles(messageId)` then `createForkFromInput` at that message.

<Warning>
Rewind-fork clears `sdkSessionId` on the fork so resume does not collide with the parent SDK session.
</Warning>

## Teleport and import flows

| Flow | Direction | Entry | Main behavior |
|------|-----------|-------|---------------|
| claude.ai import | External → local | `NewSessionDialog` teleport step | `dev.createTeleportSession({ sessionId, name, cwd })`; sets `isTeleported` when applicable |
| SSH teleport | Local → remote | `TeleportDialog` + `SessionList` | `ssh.teleportSession(sourceId, SSHConfig)` uploads `~/.claude/projects` transcripts and syncs settings |
| Download | Remote → local | `DownloadSessionDialog` | `ssh.downloadSession`; sets `downloadedFrom` on the new local session |

Session fields `teleportedFrom` and `downloadedFrom` link provenance across hosts. SSH teleport progress surfaces through the same setup-progress listeners as session creation.

## Message queue

While the agent streams (`isStreaming` or `isProcessingQueue`), new user input enqueues instead of starting a parallel turn.

```text
User send (streaming) --> renderer messageQueue[] + main MessageQueueService
Stream ends --> onStreamEnd --> scheduleDrain(minTurnGapMs)
drain-ready --> dequeue --> queue:send-next --> session.store sendMessage
```

| Surface | Role |
|---------|------|
| `queue:enqueue` / `remove` / `edit` / `moveToFront` / `clear` | Main-process queue mutations |
| `queue:state-changed` | Syncs renderer `messageQueue` display |
| `MessageQueuePanel` | Edit, reorder, clear, **interrupt** (lightning) via `interruptAndSend` |

`interruptAndSend` cancels the active stream, preserves partial assistant content, drains stale events (~300ms), then sends the queued message immediately.

### Coalescing

Two mechanisms reduce duplicate turns:

1. **Early follow-up coalescing** (renderer): If the user sends again while streaming but before visible assistant activity, `sendMessage` merges prompts via `combineUserPrompts`, cancels the in-flight turn, and resends once.

2. **Harness turn gap** (main): After `onStreamEnd`, `minTurnGapMs` delays drain (`0` for `claude`, `500` for codex/cursor/gemini/opencode/custom). `maxCoalesceWindowMs` is defined per harness in `harness-capabilities.ts` for future use; the active coalescing path today is the renderer early-follow-up branch.

Queued user bubbles render immediately with a queued indicator so sends are visible before drain.

## Permission and plan approval dialogs

### Permission requests

When the harness emits a tool permission request, `session.store` sets `pendingPermission[sessionId]`. `PermissionDialog` renders above the chat input (also in `CommandCenterCell` and `AgentView`) with:

| Action | Effect |
|--------|--------|
| Approve | `claude.respondToPermission` with optional modified input |
| Always approve | Bash pattern wildcard (e.g. `gh pr *`) |
| Deny | Rejects the tool call |
| BUILD IT! | Sets permission mode to `bypassPermissions` and approves |

`getSessionPriority` treats sessions with pending permission/question/plan as `needs-input`. SSH disconnect clears stale permission/question dialogs for that session.

### Plan approval

`ExitPlanMode` triggers `CLAUDE_PLAN_APPROVAL_REQUEST`. The store sets `pendingPlanApproval`, copies plan content, and opens `PlanPanel` for the active session (background sessions keep the request but do not auto-open the panel). Approve/reject call `claude.respondToPlanApproval` with `PlanApprovalResponse { requestId, approved, feedback? }`. Ultra Plan settings can auto-decompose tasks after approval (see settings reference).

## Session switcher UX

`SessionSwitcher` + `useSessionSwitcher` implement an Alt-Tab-style overlay for **running** sessions only (sorted by `updatedAt`).

| Input | Behavior |
|-------|----------|
| `Ctrl+Tab` | Open switcher; further tabs cycle forward |
| `Ctrl+Shift+Tab` | Cycle backward |
| Release `Ctrl` | Confirm selection → `setActiveSession` |
| `Esc` | Cancel without switching |
| IPC `app.onSessionSwitcher` | Same actions when a webview has focus (main forwards Ctrl+Tab from `before-input-event`) |

The overlay shows color blocks, status dots, branch labels, an **ACTIVE** badge, and a shortcut into Command Center.

## Sidebar session list

`SessionList` groups sessions by local project path or SSH host. Conversation forks with `parentSessionId` are hidden unless they are the active session (so the sidebar stays uncluttered while `ForkTabs` handles fork navigation). Starred sessions support drag reorder; teleport/download actions open their respective dialogs.

## IPC quick reference

| Channel | Pattern | Purpose |
|---------|---------|---------|
| `session:create` | invoke | GitHub clone session |
| `session:start` / `session:stop` | invoke | Status activation |
| `session:delete` | invoke | Remove session + service cleanup |
| `session:list` / `session:get` / `session:update` | invoke | Query/mutate records |
| `session:rewind-fork` | invoke | Transcript truncate + new session |
| `session:create-fork` | invoke | SDK conversation fork |
| `session:get-fork-group` | invoke | Root + children for tabs |
| `session:scan-remote` | invoke | SSH orphan transcript discovery |
| `session:status-changed` | push | Per-session status updates |
| `session:list-updated` | push | Full list refresh |
| `queue:*` | invoke + `queue:state-changed` push | Message queue |
| `queue:send-next` | push | Dequeued message ready to send |
| `claude:plan-approval-request` / `response` | push / invoke | Plan mode gate |

Preload exposes these as `window.electronAPI.sessions.*`, `window.electronAPI.claude.onQueueSendNext`, and related helpers.

## State diagram (activation)

```mermaid
stateDiagram-v2
  [*] --> stopped: create / discover
  stopped --> running: start OR setActiveSession
  running --> stopped: stop
  creating --> stopped: clone success
  creating --> error: clone failure
  error --> [*]: delete
  stopped --> [*]: delete
  running --> [*]: delete
```

<Tip>
Prefer `dev.createSession` for day-to-day local work; `session:create` is the Docker-era GitHub clone path. Both persist into the same `claudette-sessions` store namespace.
</Tip>

## Related pages

<CardGroup>
<Card title="Sessions and workspaces" href="/sessions-and-workspaces">
Session schema, worktrees, discovery, and electron-store layout.
</Card>
<Card title="IPC and preload bridge" href="/ipc-bridge">
How `session:*` channels map to `window.electronAPI`.
</Card>
<Card title="SSH remote sessions" href="/ssh-remote-sessions">
Teleport, download, resume candidates, and remote transcript scan.
</Card>
<Card title="Harnesses and models" href="/harnesses-and-models">
Permission modes and harness queue capabilities.
</Card>
</CardGroup>
