commands/session/ — Session Management Commands
Remote session display, conversation persistence, and cross-session navigation
The /session command displays a QR code and URL for the active remote session. The broader session ecosystem includes /resume, /export, and /branch commands for listing, resuming, exporting, and forking conversation sessions stored as JSONL transcripts.
📚 What is this & why does it exist?
The session module is the persistence backbone of Claude Code conversations. Every conversation is a "session" identified by a UUID and stored as a JSONL file. The /session command itself focuses on remote access—displaying a QR code so users can connect to a headless Claude Code instance from another device. The broader session ecosystem (/resume, /export, /branch) enables users to pick up where they left off, share transcripts, and fork conversations into new exploration paths.
💡 Real-world analogy
Sessions work like a journal with numbered notebooks. Each conversation is a notebook (JSONL file) stored on a shelf organized by project. /session is like showing a QR code on the notebook cover so someone else can scan it and read along remotely. /resume is pulling a specific notebook off the shelf and reopening it to the last page. /export is photocopying the notebook into a clean plaintext printout. /branch is duplicating the notebook up to a certain page and starting fresh from there—the new copy tracks which original it was forked from.
Contents
Architecture
flowchart TD SESSION["/session command"] -->|"load()"| STSX["session.tsx"] STSX -->|"useAppState()"| RURL["remoteSessionUrl"] STSX -->|"qrToString()"| QR["QR Code Display"] STSX -->|"useKeybinding(ESC)"| DONE["onDone()"] RESUME["/resume command"] -->|"loadAllProjectsMessageLogs()"| LOGS["Session JSONL Files"] RESUME -->|"loadFullLog()"| FULL["Full Transcript"] RESUME -->|"restoreSessionMetadata()"| RESTORE["Session Restore"] EXPORT["/export command"] -->|"renderMessagesToPlainText()"| TXT["Plaintext File"] BRANCH["/branch command"] -->|"createFork()"| FORK["Forked JSONL"] FORK -->|"forkedFrom metadata"| LINEAGE["Fork Lineage"] LOGS -->|"~/.claude/sessions/"| DISK["Filesystem"] LOGS -->|".index/ lite logs"| LITE["Lite Log Index"]
Session Lifecycle
sequenceDiagram
participant U as User
participant B as Bootstrap
participant S as SessionStorage
participant A as AppState
U->>B: claude --remote
B->>A: Set remoteSessionUrl
B->>S: Create session JSONL
Note over S: ~/.claude/sessions/{projectHash}/{id}.jsonl
U->>U: /session
A-->>U: Display QR + URL
U->>U: /resume
S-->>U: List sessions (lite logs)
U->>S: Select session
S->>S: loadFullLog() if lite
S->>A: restoreSessionMetadata()
U->>U: /export
S-->>U: renderMessagesToPlainText()
U->>U: /branch
S->>S: createFork() with new UUID
Note over S: Copies messages up to branch point
Key Classes & Functions
| Class / Function | File | Purpose |
|---|---|---|
call() | session.tsx | Entry point: renders <SessionInfo> component showing QR and URL |
SessionInfo | session.tsx | React component: reads remoteSessionUrl from AppState, generates QR |
getIsRemoteMode() | bootstrap/state.ts | Returns whether Claude Code was launched with --remote flag |
getSessionId() | bootstrap/state.ts | Returns the current session UUID |
switchSession() | bootstrap/state.ts | Switches the active session to a different session ID |
loadAllProjectsMessageLogs() | sessionStorage.ts | Loads session metadata from all projects for the resume picker |
loadSameRepoMessageLogs() | sessionStorage.ts | Loads sessions matching current repository worktree paths |
loadFullLog() | sessionStorage.ts | Expands a lite log into the full transcript with all messages |
isLiteLog() | sessionStorage.ts | Checks if a log entry is a compact index vs full transcript |
saveCustomTitle() | sessionStorage.ts | Persists a user-chosen title for a session |
restoreSessionMetadata() | sessionStorage.ts | Restores attribution, file history, and state for a resumed session |
renderMessagesToPlainText() | exportRenderer.ts | Converts session messages to a human-readable plaintext format |
createFork() | branch.ts | Duplicates a session up to a branch point with new UUID and forkedFrom |
parseSessionIdentifier() | sessionUrl.ts | Parses session input as JSONL path, UUID, or URL |
qrToString() | qrcode (lib) | Converts a URL to UTF-8 ASCII art QR code |
Data Structures
| Type | Name | Description |
|---|---|---|
| type | LogOption | Session metadata: sessionId, sessionTitle, lastUpdated, cwd, branch, isLiteLog |
| type | ParsedSessionUrl | Parsed identifier: sessionId, ingressUrl, isUrl, jsonlFile, isJsonlFile |
| type | TranscriptMessage | Single message: type (user/assistant/system), message, uuid, timestamp, cwd |
| type | LocalJSXCommandCall | Command entry signature: (onDone, context?, args?) => Promise<ReactNode> |
| format | JSONL | JSON Lines format—each line is one entry (message, snapshot, metadata) |
| state | remoteSessionUrl | AppState field: URL for remote session access (undefined if local) |
| state | remoteConnectionStatus | AppState field: 'connecting' | 'connected' | 'reconnecting' | 'disconnected' |
| path | ~/.claude/sessions/{hash}/{id}.jsonl | On-disk session storage path, organized by project hash |
| path | .index/ | Lite log index folder for fast session search without full load |
Interactions
| Direction | Partner | Interface | Description |
|---|---|---|---|
| → | state/AppState | useAppState(s => s.remoteSessionUrl) | Reads the remote session URL for QR code generation |
| → | bootstrap/state | getIsRemoteMode() | Gates command visibility—only shown when --remote is active |
| → | bootstrap/state | getSessionId() / switchSession() | Get current session ID or switch to a resumed session |
| → | sessionStorage | loadAllProjectsMessageLogs() | Discovers all sessions across projects for the resume picker |
| → | sessionStorage | loadFullLog() | Expands a lite log into full transcript on demand |
| → | exportRenderer | renderMessagesToPlainText() | Serializes messages for /export output |
| → | qrcode | qrToString(url, opts) | Generates ASCII QR code with error correction level L |
| → | hooks/useKeybinding | useKeybinding('escape', onDone) | Dismisses the session info panel on Escape |
| ← | commands.ts registry | default export | Command metadata with aliases: ['remote'], conditional isEnabled |
| ↔ | getWorktreePaths | getWorktreePaths() | Resolves project paths for cross-project resume validation |
Configuration
| Knob / Parameter | Default | Description |
|---|---|---|
--remote | false | CLI flag that enables remote session mode and makes /session visible |
type: 'utf8' | utf8 | QR code output type (UTF-8 ASCII art for terminal display) |
errorCorrectionLevel: 'L' | L | QR error correction level (Low = smaller code, faster render) |
~/.claude/sessions/ | — | Base directory for all session JSONL files, organized by project hash |
.index/ | — | Lite log index subfolder for fast session discovery |
forkedFrom | — | Metadata field in branched sessions tracking parent session ID |
sessionTitle | auto-derived | Custom or auto-derived title from first user prompt |
Directory Map
Source tree for session
session/
├── index.ts ← Command registration (type: 'local-jsx', aliases: ['remote'])
└── session.tsx ← SessionInfo component: QR code + URL display (~140 lines)
Related commands (separate directories):
├── resume/ ← /resume: session listing, search, and restore
├── export/ ← /export: plaintext transcript export
└── branch/ ← /branch: fork conversation with lineage tracking
Deep-Dive Highlights
Core mechanisms from the session ecosystem have been extracted into dedicated focus areas for in-depth exploration.
JSONL Persistence Engine Focus
Append-only JSONL storage, lite log indexing for fast search, on-demand full log expansion, and per-project hash-based directory organization.
Session Resume Flow Focus
Cross-project discovery, worktree validation, metadata restoration (attribution, file history, todo lists), and session switching via bootstrap state.
Fork & Branch Tracking Focus
Session duplication up to a branch point, forkedFrom lineage metadata, new UUID generation, and first-prompt derivation for branch titles.
Remote Session & QR Access Focus
Remote mode detection, AppState URL propagation, QR code generation with UTF-8 encoding, and connection status tracking.
Code Examples
Example 1: Command Registration (Conditional Visibility)
// index.ts — Session command with conditional enablement
import type { Command } from '../../commands.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
const session = {
type: 'local-jsx',
name: 'session',
aliases: ['remote'],
description: 'Show remote session URL and QR code',
isEnabled: () => getIsRemoteMode(),
isHidden: !getIsRemoteMode(),
load: () => import('./session.js'),
} satisfies Command
export default session
Example 2: SessionInfo Component with QR Generation
// session.tsx — SessionInfo component (simplified)
import React, { useState, useEffect } from 'react'
import { Box, Text } from 'ink'
import { useAppState } from '../../state/AppState.js'
import { useKeybinding } from '../../hooks/useKeybinding.js'
import { qrToString } from 'qrcode'
import { Pane } from '../../components/Pane.js'
function SessionInfo({ onDone }: { onDone: () => void }) {
const remoteSessionUrl = useAppState(s => s.remoteSessionUrl)
const [qrCode, setQrCode] = useState<string | null>(null)
// Generate QR code when URL becomes available
useEffect(() => {
if (!remoteSessionUrl) return
qrToString(remoteSessionUrl, {
type: 'utf8',
errorCorrectionLevel: 'L',
}).then(setQrCode)
}, [remoteSessionUrl])
// Dismiss on Escape
useKeybinding('escape', onDone)
if (!remoteSessionUrl) {
return <Text color="yellow">No remote session active</Text>
}
return (
<Pane title="Remote Session">
<Box flexDirection="column" alignItems="center">
{qrCode && <Text>{qrCode}</Text>}
<Text color="cyan">{remoteSessionUrl}</Text>
<Text dimColor>Press ESC to dismiss</Text>
</Box>
</Pane>
)
}
export const call: LocalJSXCommandCall = async (onDone) => {
return <SessionInfo onDone={onDone} />
}
Example 3: Session Identifier Parsing
// sessionUrl.ts — Accepts 3 formats for session identification
import { parseSessionIdentifier } from '../utils/sessionUrl.js'
// Format 1: JSONL file path
const fromFile = parseSessionIdentifier('/path/to/export.jsonl')
// => { jsonlFile: '/path/to/export.jsonl', isJsonlFile: true, ... }
// Format 2: UUID
const fromUuid = parseSessionIdentifier('550e8400-e29b-41d4-a716-446655440000')
// => { sessionId: '550e8400-...', isUrl: false, isJsonlFile: false }
// Format 3: Remote URL
const fromUrl = parseSessionIdentifier('https://api.example.com/v1/session/abc')
// => { sessionId: 'abc', ingressUrl: 'https://api.example.com', isUrl: true }