# Sessions and workspaces

> Session schema, lifecycle statuses, Docker vs local dev vs SSH vs OpenClaw, worktrees, forks, transcript resumption, and electron-store persistence (claudette-sessions).

- 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/shared/types/index.ts`
- `src/main/services/session.service.ts`
- `src/main/services/docker.service.ts`
- `src/main/services/transcript.service.ts`
- `src/main/ipc/session.ipc.ts`
- `src/renderer/components/session/SessionList.tsx`
- `src/renderer/stores/session.store.ts`

---

---
title: "Sessions and workspaces"
description: "Session schema, lifecycle statuses, Docker vs local dev vs SSH vs OpenClaw, worktrees, forks, transcript resumption, and electron-store persistence (claudette-sessions)."
---

Build models every coding conversation as a `Session` record in the main process, keyed by UUID and persisted under `claudette-sessions` via `CachedStore`. The active workspace path (`worktreePath`), optional remote or gateway configuration, lifecycle `status`, and transcript identity (`sdkSessionId` / `sdkSessionMappings`) determine where harnesses run, which files agents see, and how chat history resumes after restart.

## Session schema

The canonical type lives in `src/shared/types/index.ts`. A session always has filesystem anchors and ports; mode-specific fields are optional.

| Field | Role |
| --- | --- |
| `id` | Build-local UUID (or transcript-derived id for discovered sessions) |
| `name` | Display name; may be overridden by `sessionNames.{id}` in the store |
| `repoPath` | Primary repo path (local disk or SSH remote path string) |
| `worktreePath` | Directory harnesses and terminals use as `cwd` |
| `branch` | Git branch label (best-effort) |
| `status` | Lifecycle state (see below) |
| `ports` | `PortAllocation`: `web`, `api`, `debug` |
| `isDevMode` | `true` for local folder, SSH, OpenClaw, teleport, and discovered sessions (no Docker container lifecycle on start/stop) |
| `sshConfig` | Present for SSH remote workspaces |
| `openclawConfig` | Present for OpenClaw Gateway sessions (`gatewayUrl`, `gatewayPassword`) |
| `containerId` | Optional Docker container when a GitHub-clone session still has container infrastructure |
| `sdkSessionId` | Claude Agent SDK / Claude Code transcript id for resume |
| `relatedSessionIds` | Alternate ids mapping to the same conversation |
| `continuedFromSessionId` | Local session resumed when creating SSH from a local transcript |
| `isWorktree` / `parentRepoPath` / `forkName` | Git worktree fork metadata |
| `parentSessionId` / `childSessionIds` / `forkPoint` / `aiGeneratedName` | Conversation fork graph (separate from git worktrees) |
| `isTeleported` | Imported from claude.ai via CLI teleport |
| `worktreeInstructions` / `worktreeInstructionsSent` | `.claudette/worktree-setup.md` content injected on first turn |
| `setupOutput` / `errorMessage` | SSH setup logs or failure text |
| `isStarred` / `starredAt` / `tabHidden` | Sidebar ordering and visibility |

<ParamField body="openclawConfig.gatewayUrl" type="string" required>
Base URL of the OpenClaw gateway (for example `http://localhost:18789`).
</ParamField>

<ParamField body="sshConfig.remoteWorkdir" type="string" required>
Remote working directory; also used as initial `worktreePath` until setup scripts rewrite it.
</ParamField>

## Lifecycle statuses

```typescript
export type SessionStatus =
  'creating' | 'starting' | 'setup' | 'running' | 'stopping' | 'stopped' | 'error';
```

```mermaid
stateDiagram-v2
  [*] --> creating: GitHub clone / SSH create
  creating --> stopped: Clone finished (GitHub)
  creating --> running: Local dev / OpenClaw / teleport
  creating --> setup: SSH async setup
  setup --> running: Remote connect OK
  setup --> error: SSH setup failed
  stopped --> running: startSession()
  running --> stopped: stopSession()
  creating --> error: Clone or setup failure
  error --> [*]
```

<Note>
`startSession` and `stopSession` today only flip `status` between `running` and `stopped`; they do not start Docker containers. SSH creation uses `creating` → async connect/setup → `running` or `error`, with `SetupProgressEvent` pushed on `SSH_SETUP_PROGRESS`.
</Note>

Local dev sessions created through `DEV_CREATE_SESSION` are stored immediately as `running`. GitHub `SESSION_CREATE` ends in `stopped` after clone; the user starts explicitly.

## Workspace modes

Build supports four distinct workspace backends. All share the same `Session` shape; presence of flags/config selects behavior in services and UI.

```text
                    ┌─────────────────────────────────────┐
                    │         SessionService + IPC        │
                    │   sessions.{id}  sdkSessionMappings │
                    └─────────────────────────────────────┘
           ┌────────┴────────┬────────────┬───────────────┴────────┐
           ▼                 ▼            ▼                        ▼
    Local dev (folder)   GitHub clone   SSH remote            OpenClaw Gateway
    isDevMode: true      ports + clone  sshConfig             openclawConfig
    worktree optional    userData/      isDevMode: true       repoPath: ""
    ~/.claudette/        sessions/      remote workdir        isDevMode: true
    worktrees/           + Docker*      async setup
```

### Local dev (open project folder)

Entry: **New session → Local folder** → `IPC_CHANNELS.DEV_CREATE_SESSION` in `dev.ipc.ts`.

- `repoPath` and default `worktreePath` are the chosen folder.
- `isDevMode: true`, `status: 'running'`, fixed placeholder ports (`3000` / `8080` / `9229`).
- Optional **git worktree fork**: when `createWorktree` is set, Build resolves the main repo, runs `git worktree add` into `~/.claudette/worktrees/{repo-hash}/wt-{id-prefix}`, sets `isWorktree`, `parentRepoPath`, and a memorable `forkName`.
- Project hooks: `.claudette/worktree-setup.sh` (executed in the worktree) or `.claudette/worktree-setup.md` (stored on the session and sent as the first agent message when not yet sent).

Discovered sessions (see below) also set `isDevMode: true` when scanned from Claude Code transcripts under `~/.claude/projects` (or `CLAUDE_PROJECTS_DIR`).

### Docker / GitHub clone

Entry: **New session → GitHub** → `SESSION_CREATE` → `SessionService.createSession`.

- Clones into `{userData}/sessions/{sessionId}/{repoName}`.
- Allocates host ports via `DockerService.allocatePorts` (`BASE_PORT` 10000, 10 ports per session).
- Writes `.grep/setup.sh` from `setupScript` (default installs npm/pip deps).
- Ends at `stopped` (or `error` with `errorMessage`).
- `deleteSession` still stops/removes `containerId` and releases ports if present.

`DockerService` can build images, bind `worktreePath` to `/workspace`, and expose 3000/8000/9229 — used when `containerId` exists (terminals use `docker exec`). Current `startSession` does **not** call `startContainer`; container lifecycle is legacy/cleanup-oriented unless another path sets `containerId`.

### SSH remote

Entry: **New session → SSH** → `SSH_CREATE_SESSION` in `ssh.ipc.ts`.

- Session persisted immediately with `status: 'creating'`, `isDevMode: true`, zero ports.
- `sdkSessionMappings.{id}` is `resumeSessionId`, `'new'` (fresh remote), or mapped on resume.
- Optional `continuedFromSessionId` when resuming from a matching local session.
- Background: `sshService.connect`, optional `worktreeScript` / `syncSettings` pre-setup, then `running` or `error` with `setupOutput`.
- `worktreePath` / `repoPath` may be rewritten to the directory returned by remote setup.
- `SESSION_SCAN_REMOTE` lists orphan remote transcripts and materializes hidden child sessions (`tabHidden: true`) linked via `parentSessionId`.

Resume candidates: `SSH_LIST_RESUME_CANDIDATES` probes `~/.claude/projects/...` on the remote workdir.

### OpenClaw Gateway

Entry: **New session → OpenClaw** → `OPENCLAW_CREATE_SESSION` in `openclaw.ipc.ts`.

- Empty `repoPath` / `worktreePath`, `openclawConfig` required, `isDevMode: true`, `status: 'running'`.
- `sdkSessionMappings.{id}` = `'new'` so message load does not pull unrelated transcripts.
- `claude.service` routes streaming to `openclawService.streamAsChat` when `session.openclawConfig` is set (BYOC: any gateway URL and bearer token you configure).

## Git worktrees vs conversation forks

These are **different** concepts in the schema.

| Concept | Fields | Mechanism |
| --- | --- | --- |
| **Git worktree fork** | `isWorktree`, `parentRepoPath`, `forkName` | `git worktree add` under `~/.claudette/worktrees/`; sidebar nests under parent repo in `SessionList` |
| **Conversation fork** | `parentSessionId`, `childSessionIds`, `forkPoint`, `aiGeneratedName` | SDK `forkSession()` locally; SSH uses `resume` + `forkSession: true` on first query |
| **Rewind fork** | New `id`, truncated transcript | `SESSION_REWIND_FORK` copies Claude JSONL + `transcriptService.cloneTranscript`; clears `sdkSessionId` on the fork to avoid resume conflicts |

`SESSION_CREATE_FORK` and `SESSION_GET_FORK_GROUP` implement the conversation fork graph. `ForkTabs` in the renderer shows tabs; the sidebar hides `parentSessionId` forks unless that fork is the active session.

## Transcript resumption

Build keeps **three** related stores:

1. **Claude Code / SDK JSONL** — `~/.claude/projects/{encoded-path}/{sessionId}.jsonl` (override root with `CLAUDE_PROJECTS_DIR`).
2. **Build canonical transcript** — `~/.build/transcripts/{sessionId}.jsonl` via `TranscriptService` (all harnesses, append-only JSONL).
3. **Session store mappings** — `sdkSessionMappings` in `claudette-sessions`: local Build id → SDK transcript id.

On list/load, `SessionService.getMergedSessions()` merges **stored** sessions (`sessions.{id}`) with **discovered** cache (`discoveredSessions` + scan), attaches `sdkSessionId` from mappings, and deduplicates ids through `resolveBuildSessionIdForDiscoveredSession`.

Resume behavior in `claude.service`:

- Resolves SDK id from `session.sdkSessionId` or `sdkSessionMappings`.
- Sentinel `'new'` means no resume until the first `system_id` stores the real id.
- Auto Build (`model === 'auto'`) may skip SDK resume when the last harness was not Claude.
- SSH forks may keep `forkFromSdkSessionId` until the first query forks server-side.

Teleport (`DEV_CREATE_TELEPORT_SESSION`) runs `claude --teleport` for web `session_*` ids, then stores the session with `isTeleported: true` and mapping to the remote/local transcript id.

## `claudette-sessions` persistence

| Store key | Contents |
| --- | --- |
| `sessions.{sessionId}` | Full `Session` objects (requires `name` and `repoPath` to appear in `getSessions`) |
| `sdkSessionMappings.{localId}` | SDK/transcript id, or `'new'` |
| `sessionNames.{sessionId}` | AI-generated display names (Haiku) |
| `discoveredSessions` | Cached discovery snapshot for fast startup |
| `lastDiscoveryTimestamp` | Cache freshness |

Implementation: `CachedStore` wraps `electron-store` with in-memory cache and debounced disk writes (2s) to avoid blocking the main process on large session files.

**Dev vs production separation** is by **userData directory**, not store file name: `./scripts/dev.sh` sets `GREP_DEV_USER_DATA=/tmp/grep-build-dev`, and `src/main/index.ts` calls `app.setPath('userData', …)` so the same `claudette-sessions` filename lives in an isolated folder. Production uses `~/Library/Application Support/Build` (with migration from legacy `Grep Build` / `G-Build` dirs).

Startup maintenance:

- **Prune**: sessions older than 14 days (non-starred) removed from `sessions.*`.
- **Worktree migration**: paths under `.claudette/worktrees/` backfill `isWorktree` / `parentRepoPath`.

Active session id for the renderer is stored separately in `claudette-settings` via `dev.getActiveSession` / `dev.setActiveSession`.

## Renderer integration

`session.store.ts` loads `window.electronAPI.sessions.list()`, restores active session from dev settings, auto-selects the newest `running` session if none active, and calls `loadMessages` for the active id. `withMaterializedSession` re-invokes `sessions.update(sessionId, {})` when IPC returns "session not found" so discovered ids can be promoted to stored records.

`SessionList.tsx` groups sessions into:

- **SSH hosts** (`sshConfig.host`),
- **Local projects** by `worktreePath` (with nested **worktree forks** under `parentRepoPath`),
- **Recent** non-fork sessions,
- **Starred** reorderable list.

Conversation forks are omitted from the tree unless active (so fork tabs carry navigation).

## IPC surface (session domain)

| Channel | Behavior |
| --- | --- |
| `SESSION_CREATE` | GitHub clone session |
| `SESSION_START` / `SESSION_STOP` | Status toggle |
| `SESSION_DELETE` | Remove store, optional Docker cleanup |
| `SESSION_LIST` / `SESSION_GET` / `SESSION_UPDATE` | CRUD + merge |
| `SESSION_REWIND_FORK` | Truncated transcript fork |
| `SESSION_CREATE_FORK` / `SESSION_GET_FORK_GROUP` | Conversation forks |
| `SESSION_SCAN_REMOTE` | SSH orphan transcript import |
| `DEV_CREATE_SESSION` | Local / worktree session |
| `DEV_CREATE_TELEPORT_SESSION` | claude.ai import |
| `SSH_CREATE_SESSION` | Remote workspace |
| `OPENCLAW_CREATE_SESSION` | Gateway workspace |

Events: `SESSION_STATUS_CHANGED`, `SESSION_LIST_UPDATED` broadcast to all windows.

## Verification signals

| Check | Expected |
| --- | --- |
| New local session | `sessions.{uuid}` in dev userData; `isDevMode: true`; `status: 'running'` |
| SSH connect | Setup progress in chat; `status: 'running'`; `worktreePath` matches remote cwd |
| Resume | `sdkSessionMappings` points to existing JSONL; next message continues transcript |
| Worktree fork | Path under `~/.claudette/worktrees/{hash}/wt-*`; `isWorktree: true` |
| List after restart | `discoveredSessions` + merge shows Claude Code sessions without re-scan blocking UI |

<Warning>
Updating fields on a **discovered-only** session (no `sessions.{id}` row) is ignored by `updateSession` except via materialization. Starred SSH sessions should be stored rows so metadata survives merge.
</Warning>

## Related pages

<CardGroup>
  <Card title="Manage coding sessions" href="/manage-sessions">
    Create, start, stop, fork, rewind, teleport, and permission flows from the UI.
  </Card>
  <Card title="SSH remote sessions" href="/ssh-remote-sessions">
    SSHConfig, resume candidates, download/teleport, and bridge recovery.
  </Card>
  <Card title="Git workflows" href="/git-workflows">
    Per-session worktree git operations and GitExplorer bindings.
  </Card>
  <Card title="Shared types reference" href="/shared-types-reference">
    Full `Session`, `SSHConfig`, `OpenClawConfig`, and message-queue types.
  </Card>
  <Card title="IPC channels reference" href="/ipc-channels-reference">
    Complete `SESSION_*`, `DEV_*`, `SSH_*`, and `OPENCLAW_*` channel catalog.
  </Card>
</CardGroup>
