Introduction
Software development rarely happens in a single session. You might spend days or weeks working on a feature, fixing bugs, or refactoring code. In traditional scenarios, you'd need to give context to your AI assistant each time you start a new session, explaining what you were working on and where you left off. This repetition wastes time and can lead to inconsistencies in approach.
Claude Code solves this problem with its conversation resumption feature, allowing you to pick up exactly where you left off—with all the context, decisions, and nuances of your previous interactions preserved. This capability transforms Claude from a session-based tool into a persistent development partner that evolves with your project.
Tutorial Overview
In this tutorial, we'll explore how to effectively resume previous conversations with Claude Code. We'll go beyond the basic commands to show you strategies for maintaining context across complex, long-running projects, and how to leverage Claude's memory to build more coherent codebases.
What You'll Learn
- Basic conversation resumption with Claude Code
- Selecting from multiple previous conversations
- Strategies for maintaining context across sessions
- Non-interactive resumption for automated workflows
- Techniques for multi-day development projects
Requirements
- Claude Code CLI installed
- Previous Claude Code conversations to resume
- A project you're actively developing
Basic Conversation Resumption
Let's start with the fundamentals of resuming conversations with Claude Code. There are two main approaches: automatically continuing the most recent conversation or selecting from multiple past conversations.
Automatically Continue the Most Recent Conversation
The simplest way to resume your most recent conversation with Claude Code is to use the --continue
flag:
claude --continue
This command automatically loads your most recent conversation without any prompts or selection screens. It's perfect when you're working on a single project and need to quickly pick up where you left off.
Select from Multiple Previous Conversations
If you've been working on multiple projects or features, you might need to select which conversation to resume. Use the --resume
flag to see a list of your recent conversations:
claude --resume
This displays a conversation picker that shows each conversation with a brief snippet of content to help you identify it:
Example conversation picker output:
1. 2023-05-12 14:30 - "Refactoring the authentication system to use OAuth..." 2. 2023-05-11 10:15 - "Implementing the product filtering component..." 3. 2023-05-10 16:45 - "Setting up the database schema for user profiles..."
Simply enter the number of the conversation you want to resume.
Organizing and Managing Multiple Conversations
When working on multiple features or projects, keeping your conversations organized becomes crucial. Here are some strategies for effective conversation management.
Start Each Conversation with a Clear Subject Line
Begin each new Claude Code session with a descriptive subject that clearly identifies the project or feature you're working on:
claude
# Begin with a clear project identifier
Human: [AUTH SYSTEM REFACTOR] I need help refactoring our authentication system to use OAuth 2.0 instead of the current JWT implementation.
This makes the conversation easily identifiable in the resume picker, as it will display the first part of your message.
Use Project-Specific Directories
Create dedicated directories for different projects or features, and always launch Claude Code from the relevant directory:
# For feature A
cd ~/projects/myapp/feature-a
claude
# For feature B
cd ~/projects/myapp/feature-b
claude
Claude Code's conversation history is associated with your current working directory, making it easier to resume the right conversation later.
End Sessions with Clear Summaries
Before ending a conversation, ask Claude to summarize what you've accomplished and what's next. This creates a helpful reference point when you resume:
Human: Please summarize what we've accomplished in this session and outline the next steps for our OAuth implementation.
Custom Example: Multi-Session Development of a Real-Time Collaborative Editor
Let's explore a detailed real-world scenario where you're implementing a complex feature over several days, using Claude Code's conversation resumption capabilities to maintain consistent context and approach throughout the development cycle.
Scenario Description
You're building a real-time collaborative document editing system, similar to Google Docs, using React for the frontend and Node.js with Express for the backend. This complex feature requires implementing several interconnected components:
- A WebSocket-based connection system with Socket.io
- Operational transformation (OT) algorithms for conflict resolution
- User presence indicators with cursor tracking
- Permission controls for document access and editing
- Document history and versioning system
Each of these components will take a day to implement, and maintaining architectural consistency across the entire system is critical for performance and maintainability.
Project Architecture Overview
Before diving into the day-by-day implementation, let's understand the high-level architecture of our collaborative editor:
- Frontend: React application with state management using Redux for document data and React Context for real-time communication
- Backend: Express server with Socket.io for real-time communication and MongoDB for document storage
- Real-time Communication: Socket.io for bidirectional, event-based communication
- Data Consistency: Operational Transformation (OT) algorithms to resolve conflicts between concurrent edits
- Security: JWT authentication and document-level permission system
Implementation Steps
Day 1: Initial Architecture Planning and WebSocket Implementation- Step 1: Start a new Claude Code session with detailed context
cd ~/projects/collab-editor claude
Human: [COLLABORATIVE EDITOR PROJECT] I'm building a real-time collaborative document editor using React and Node.js with Socket.io. I need help designing the initial architecture and implementing the WebSocket connection system. The editor should support multiple users editing the same document simultaneously with real-time updates, cursor tracking, and conflict resolution using operational transformation. Claude: I'll help you design and implement a real-time collaborative document editor. Let's start by planning the architecture and then implement the WebSocket connection system with Socket.io. First, let's outline the high-level architecture for this collaborative editor: **Frontend Architecture:** - React application with component-based UI - Redux for global document state management - Socket.io client for real-time communication - Custom hooks for document operations and synchronization **Backend Architecture:** - Node.js with Express server - Socket.io for WebSocket communication - Document storage in MongoDB with Mongoose - Authentication and authorization middleware - Operational Transformation (OT) conflict resolution system Let's begin by implementing the WebSocket connection system. I'll show you the essential code for both the client and server sides. **Server-side WebSocket Implementation:** First, set up your basic Express server with Socket.io: ```javascript // server.js const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const mongoose = require('mongoose'); const cors = require('cors'); // Initialize Express const app = express(); app.use(cors()); app.use(express.json()); // Create HTTP server and Socket.io instance const server = http.createServer(app); const io = new Server(server, { cors: { origin: 'http://localhost:3000', // Your React app URL methods: ['GET', 'POST'], credentials: true } }); // Connect to MongoDB mongoose.connect('mongodb://localhost:27017/collab-editor', { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('Connected to MongoDB')) .catch(err => console.error('MongoDB connection error:', err)); // Socket.io connection handling io.on('connection', (socket) => { console.log('New client connected:', socket.id); // Handle document room joining socket.on('join-document', async (documentId) => { socket.join(documentId); console.log(`Client ${socket.id} joined document: ${documentId}`); // Fetch document from database and send to client // We'll implement this later }); // Handle document changes socket.on('document-change', (data) => { // Broadcast changes to all clients in the document room except sender socket.to(data.documentId).emit('document-update', { content: data.content, delta: data.delta, userId: data.userId, timestamp: Date.now() }); }); // Handle cursor position updates socket.on('cursor-position', (data) => { socket.to(data.documentId).emit('cursor-update', { userId: data.userId, position: data.position, username: data.username }); }); // Handle disconnection socket.on('disconnect', () => { console.log('Client disconnected:', socket.id); }); }); // Start server const PORT = process.env.PORT || 5000; server.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); ``` **Frontend WebSocket Implementation:** Now, let's implement the Socket.io client connection in your React app: ```javascript // src/services/socketService.js import { io } from 'socket.io-client'; import { updateDocument, updateCursors } from '../redux/documentSlice'; import store from '../redux/store'; class SocketService { constructor() { this.socket = null; this.documentId = null; this.userId = null; } connect(userId) { // Initialize socket connection this.socket = io('http://localhost:5000'); this.userId = userId; this.socket.on('connect', () => { console.log('Connected to Socket.io server'); }); // Listen for document updates from server this.socket.on('document-update', (data) => { // Apply received changes to the document store.dispatch(updateDocument(data)); }); // Listen for cursor position updates this.socket.on('cursor-update', (data) => { // Update cursor positions in the UI store.dispatch(updateCursors(data)); }); this.socket.on('disconnect', () => { console.log('Disconnected from Socket.io server'); }); this.socket.on('connect_error', (error) => { console.error('Socket connection error:', error); }); } joinDocument(documentId) { if (!this.socket || !this.socket.connected) { console.error('Socket not connected'); return; } this.documentId = documentId; this.socket.emit('join-document', documentId); } sendDocumentChange(content, delta) { if (!this.socket || !this.documentId) return; this.socket.emit('document-change', { documentId: this.documentId, content, delta, userId: this.userId }); } sendCursorPosition(position) { if (!this.socket || !this.documentId) return; this.socket.emit('cursor-position', { documentId: this.documentId, position, userId: this.userId, username: localStorage.getItem('username') || 'Anonymous' }); } disconnect() { if (this.socket) { this.socket.disconnect(); } } } // Create singleton instance const socketService = new SocketService(); export default socketService; ``` **Using the Socket Service in React Components:** Here's how to use this socket service in a React component: ```javascript // src/components/Editor.jsx import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import socketService from '../services/socketService'; import { setInitialDocument } from '../redux/documentSlice'; import TextEditor from './TextEditor'; const Editor = () => { const { documentId } = useParams(); const [isConnected, setIsConnected] = useState(false); const dispatch = useDispatch(); const userId = useSelector(state => state.auth.userId); useEffect(() => { // Connect to socket server when component mounts if (!isConnected && userId) { socketService.connect(userId); setIsConnected(true); } // Join the document room if (isConnected && documentId) { socketService.joinDocument(documentId); // Fetch document data from API and set in Redux fetch(`http://localhost:5000/api/documents/${documentId}`) .then(res => res.json()) .then(data => { dispatch(setInitialDocument(data)); }) .catch(err => console.error('Error fetching document:', err)); } // Cleanup on unmount return () => { if (isConnected) { socketService.disconnect(); setIsConnected(false); } }; }, [documentId, userId, isConnected, dispatch]); return ( Collaborative Document Editor Document ID: {documentId} ); }; export default Editor; ``` This implementation establishes the foundation for your collaborative editor with Socket.io. The next steps would be to implement: 1. Document storage and retrieval from MongoDB 2. Operational Transformation for conflict resolution 3. User presence indicators and cursor tracking UI 4. Permission controls Would you like me to help you implement any of these specific components next?
- Step 2: End the session with a summary
Human: We've made good progress today. Can you summarize what we've accomplished with the WebSocket implementation and outline what we should focus on tomorrow for the operational transformation component?
Claude's summary:
Claude: Here's a summary of what we've accomplished today: 1. Created a high-level architecture for the entire collaborative editor system 2. Implemented a basic Socket.io server that: - Handles client connections and disconnections - Enables users to join specific document editing sessions - Provides an initial document state to new clients - Broadcasts changes to all clients in real-time 3. Built a React frontend component that: - Connects to the Socket.io server - Joins a specific document by ID - Receives the initial document state - Sends local changes to the server - Updates when it receives changes from other users Tomorrow, we should focus on implementing the operational transformation (OT) system to handle concurrent edits properly. This will involve: 1. Designing our OT algorithm to handle text operations (insert, delete, retain) 2. Implementing transformation functions to resolve conflicts 3. Adding sequence numbers or timestamps to track edit ordering 4. Updating the server to apply OT transforms instead of naive overwrites 5. Enhancing the client to handle OT operations and track document version For tomorrow's implementation, we'll need to: - Create data structures to represent operations - Implement transformation functions - Modify the server to use the OT engine - Update the client to track and apply operations
- Step 1: Resume the previous conversation
cd ~/projects/collab-editor claude --continue
Human: I've implemented the WebSocket connection system following your guidance yesterday. Today I want to focus on the operational transformation (OT) algorithm for conflict resolution. How should we approach this? Claude: Great to hear you've implemented the WebSocket connection system! Let's now focus on implementing an Operational Transformation (OT) system for conflict resolution, which is crucial for a collaborative editor. OT is a complex topic, but I'll break it down into manageable parts and provide a practical implementation. ### Understanding Operational Transformation OT allows multiple users to edit a document simultaneously by transforming operations (like inserts and deletes) to resolve conflicts. The key idea is that when two users make concurrent changes, we transform the operations so they can be applied in sequence while preserving user intent. For our implementation, we'll: 1. Define an operation model 2. Create transformation functions 3. Implement a central server for operation processing 4. Update the client to handle and apply operations Let's start with the code implementation: **1. Operation Model** First, let's define our operation types in a shared file: ```javascript // shared/operations.js export const OperationType = { INSERT: 'insert', DELETE: 'delete', RETAIN: 'retain' }; // A basic operation class export class TextOperation { constructor() { this.ops = []; // Array of operations } // Insert operation insert(text) { if (typeof text !== 'string') { throw new Error('Insert expects a string'); } if (text === '') return this; if (this.ops.length > 0 && typeof this.ops[this.ops.length - 1] === 'string') { // Merge with previous insert this.ops[this.ops.length - 1] += text; } else { this.ops.push(text); } return this; } // Delete operation (deletes 'n' characters) delete(n) { if (typeof n !== 'number') { throw new Error('Delete expects a number'); } if (n === 0) return this; if (n < 0) throw new Error('Delete expects a positive number'); if (this.ops.length > 0 && this.ops[this.ops.length - 1] < 0) { // Merge with previous delete this.ops[this.ops.length - 1] -= n; } else { this.ops.push(-n); } return this; } // Retain operation (skips 'n' characters) retain(n) { if (typeof n !== 'number') { throw new Error('Retain expects a number'); } if (n === 0) return this; if (n < 0) throw new Error('Retain expects a positive number'); if (this.ops.length > 0 && this.ops[this.ops.length - 1] > 0) { // Merge with previous retain this.ops[this.ops.length - 1] += n; } else { this.ops.push(n); } return this; } // Apply this operation to a string apply(str) { if (typeof str !== 'string') { throw new Error('Apply expects a string'); } let newStr = ''; let strIndex = 0; for (let i = 0; i < this.ops.length; i++) { const op = this.ops[i]; if (typeof op === 'string') { // Insert operation newStr += op; } else if (op > 0) { // Retain operation if (strIndex + op > str.length) { throw new Error('Operation goes beyond string boundary'); } newStr += str.slice(strIndex, strIndex + op); strIndex += op; } else if (op < 0) { // Delete operation strIndex -= op; // op is negative, so we subtract } } return newStr; } // Transform this operation against another operation transform(other) { // We'll implement this shortly } } ``` **2. Transformation Functions** Now, let's implement the core transformation logic: ```javascript // shared/transform.js import { TextOperation } from './operations'; // Transform two operations to be applied in sequence export function transformOperations(op1, op2) { if (!(op1 instanceof TextOperation) || !(op2 instanceof TextOperation)) { throw new Error('Both arguments must be TextOperation instances'); } const op1prime = new TextOperation(); const op2prime = new TextOperation(); let op1Index = 0; let op2Index = 0; let op1Current = op1.ops[op1Index++] || 0; let op2Current = op2.ops[op2Index++] || 0; while (op1Current !== 0 || op2Current !== 0) { // Case 1: Both ops are inserts if (typeof op1Current === 'string' && typeof op2Current === 'string') { // Order by client ID to ensure consistency if (op1.clientId < op2.clientId) { op1prime.insert(op1Current); op2prime.retain(op1Current.length); op1Current = op1.ops[op1Index++] || 0; } else { op2prime.insert(op2Current); op1prime.retain(op2Current.length); op2Current = op2.ops[op2Index++] || 0; } } // Case 2: op1 inserts else if (typeof op1Current === 'string') { op1prime.insert(op1Current); op2prime.retain(op1Current.length); op1Current = op1.ops[op1Index++] || 0; } // Case 3: op2 inserts else if (typeof op2Current === 'string') { op2prime.insert(op2Current); op1prime.retain(op2Current.length); op2Current = op2.ops[op2Index++] || 0; } // Case 4: Both ops are retain/delete else { const minLength = Math.min(Math.abs(op1Current), Math.abs(op2Current)); if (op1Current > 0 && op2Current > 0) { // Both retain - just keep the retains op1prime.retain(minLength); op2prime.retain(minLength); } else if (op1Current < 0 && op2Current < 0) { // Both delete - only delete once op1prime.delete(minLength); // op2 doesn't need to delete these chars } else if (op1Current < 0 && op2Current > 0) { // op1 deletes what op2 retains op1prime.delete(minLength); // op2 doesn't need to retain (chars are gone) } else if (op1Current > 0 && op2Current < 0) { // op1 retains what op2 deletes // op1 doesn't need to retain (chars are gone) op2prime.delete(minLength); } // Update the remaining operations op1Current = op1Current > 0 ? op1Current - minLength : op1Current < 0 ? op1Current + minLength : 0; op2Current = op2Current > 0 ? op2Current - minLength : op2Current < 0 ? op2Current + minLength : 0; if (op1Current === 0) op1Current = op1.ops[op1Index++] || 0; if (op2Current === 0) op2Current = op2.ops[op2Index++] || 0; } } return [op1prime, op2prime]; } // Add the transform method to the TextOperation class TextOperation.prototype.transform = function(other) { return transformOperations(this, other)[0]; }; ``` **3. Server-Side Implementation** Now, let's update our Socket.io server to handle and transform operations: ```javascript // server/documentManager.js const { TextOperation } = require('../shared/operations'); const { transformOperations } = require('../shared/transform'); const Document = require('./models/Document'); // MongoDB model class DocumentManager { constructor() { this.documents = new Map(); // documentId -> { revision, content, operations } } async getDocument(documentId) { if (this.documents.has(documentId)) { return this.documents.get(documentId); } // Load from database if not in memory try { const doc = await Document.findById(documentId); if (!doc) return null; const documentData = { revision: doc.revision, content: doc.content, operations: [] }; this.documents.set(documentId, documentData); return documentData; } catch (err) { console.error('Error loading document:', err); return null; } } async applyOperation(documentId, clientId, operation, clientRevision) { const doc = await this.getDocument(documentId); if (!doc) throw new Error('Document not found'); // Handle concurrent modifications if (clientRevision !== doc.revision) { // Client is out of date, need to transform const concurrentOps = doc.operations.slice(clientRevision); // Transform the incoming operation against all concurrent operations let transformedOp = operation; for (const concurrentOp of concurrentOps) { const [newOp] = transformOperations(transformedOp, concurrentOp); transformedOp = newOp; } operation = transformedOp; } // Apply the operation to the document try { doc.content = operation.apply(doc.content); doc.revision++; doc.operations.push(operation); // Limit operation history to prevent memory bloat if (doc.operations.length > 100) { doc.operations = doc.operations.slice(-100); } // Save to database (async) Document.findByIdAndUpdate(documentId, { content: doc.content, revision: doc.revision }).catch(err => console.error('Error saving document:', err)); return { operation, revision: doc.revision }; } catch (err) { console.error('Error applying operation:', err); throw err; } } } module.exports = new DocumentManager(); ``` Now, let's update our Socket.io server to use this document manager: ```javascript // server.js - Updated socket handling const documentManager = require('./documentManager'); const { TextOperation } = require('../shared/operations'); // Inside the io.on('connection') handler: socket.on('join-document', async (documentId) => { socket.join(documentId); console.log(`Client ${socket.id} joined document: ${documentId}`); // Send the current document state to the client const docData = await documentManager.getDocument(documentId); if (docData) { socket.emit('document-data', { content: docData.content, revision: docData.revision }); } }); socket.on('operation', async (data) => { try { const { documentId, operation, revision, userId } = data; // Convert plain object to TextOperation instance const op = new TextOperation(); for (const item of operation.ops) { if (typeof item === 'string') { op.insert(item); } else if (item > 0) { op.retain(item); } else if (item < 0) { op.delete(-item); } } // Apply the operation to the document const result = await documentManager.applyOperation(documentId, userId, op, revision); // Broadcast the operation to all clients in the document room except sender socket.to(documentId).emit('operation', { userId, operation: result.operation, revision: result.revision }); // Acknowledge the operation to the sender socket.emit('ack-operation', { revision: result.revision }); } catch (err) { console.error('Error handling operation:', err); socket.emit('operation-error', { error: err.message }); } }); ``` **4. Client-Side Implementation** Now, let's update our client-side code to handle and apply operations: ```javascript // src/services/documentService.js import socketService from './socketService'; import { TextOperation } from '../shared/operations'; class DocumentService { constructor() { this.content = ''; this.revision = 0; this.pendingOperations = []; this.callbacks = { contentChanged: null }; // Set up socket listeners socketService.socket.on('document-data', this.handleDocumentData); socketService.socket.on('operation', this.handleRemoteOperation); socketService.socket.on('ack-operation', this.handleAckOperation); socketService.socket.on('operation-error', this.handleOperationError); } // Register callback for content changes onContentChanged(callback) { this.callbacks.contentChanged = callback; } // Handle initial document data from server handleDocumentData = (data) => { this.content = data.content; this.revision = data.revision; if (this.callbacks.contentChanged) { this.callbacks.contentChanged(this.content); } } // Handle remote operation from another client handleRemoteOperation = (data) => { const { operation, revision, userId } = data; // Convert plain object to TextOperation instance const op = new TextOperation(); for (const item of operation.ops) { if (typeof item === 'string') { op.insert(item); } else if (item > 0) { op.retain(item); } else if (item < 0) { op.delete(-item); } } // Transform any pending local operations for (let i = 0; i < this.pendingOperations.length; i++) { const [newRemoteOp, newLocalOp] = transformOperations( op, this.pendingOperations[i].operation ); this.pendingOperations[i].operation = newLocalOp; op = newRemoteOp; } // Apply the transformed operation this.content = op.apply(this.content); this.revision = revision; // Notify UI of content change if (this.callbacks.contentChanged) { this.callbacks.contentChanged(this.content); } } // Handle acknowledgment of local operation from server handleAckOperation = (data) => { const { revision } = data; // Remove the acknowledged operation from pending if (this.pendingOperations.length > 0) { this.pendingOperations.shift(); } this.revision = revision; } // Handle operation error handleOperationError = (data) => { console.error('Operation error:', data.error); // Could implement retry logic or error display here } // Apply a local operation (e.g., from user typing) applyLocalOperation(operation) { // Apply locally immediately this.content = operation.apply(this.content); // Add to pending operations this.pendingOperations.push({ operation, sentRevision: this.revision }); // Send to server socketService.socket.emit('operation', { documentId: socketService.documentId, operation: operation, revision: this.revision, userId: socketService.userId }); // Notify UI of content change if (this.callbacks.contentChanged) { this.callbacks.contentChanged(this.content); } } // Create operation from editor changes createOperationFromChange(changeObj) { // This would depend on your editor implementation // Here's a simple example for a plain text editor: const op = new TextOperation(); if (changeObj.type === 'insert') { op.retain(changeObj.index).insert(changeObj.text); } else if (changeObj.type === 'delete') { op.retain(changeObj.index).delete(changeObj.length); } else if (changeObj.type === 'replace') { op.retain(changeObj.index).delete(changeObj.oldText.length).insert(changeObj.newText); } return op; } } export default new DocumentService(); ``` This implementation provides a solid foundation for OT in your collaborative editor. It handles: 1. Concurrent edits with transformation 2. Proper versioning with revision numbers 3. Handling of out-of-order operations 4. Bidirectional sync between server and clients You'd integrate this with your editor component (using a library like Draft.js, Slate, or a custom editor) by: 1. Converting editor change events to operations 2. Applying operations from other users to your editor 3. Managing cursor positions alongside text operations Would you like me to show how to integrate this with a specific editor library or shall we move on to implementing user presence indicators tomorrow?
- Step 2: Claude remembers previous context
Notice how Claude recalls the previous day's work and continues with the same architecture and approach. This context preservation is crucial for maintaining consistency across development sessions.
Continuing the Pattern for the Entire Feature
For the remaining components (user presence, permissions, history), you can continue this pattern:
- Resume the conversation with
claude --continue
- Briefly state what you want to work on that day
- Claude will maintain context from all previous days' work
- End each session with a summary of what was accomplished and what's next
Benefits of Conversation Resumption for Complex Projects
- Architectural consistency: Decisions made on day one influence solutions on day five without requiring repetition
- Implementation continuity: Variable names, patterns, and approaches remain consistent
- Knowledge accumulation: Claude builds understanding of your codebase over time
- Reduced cognitive load: You don't need to remember all details between sessions
- Increased development velocity: Less time spent explaining context, more time implementing features
- Better project management: Summaries at the end of each session create natural progress tracking
Advanced Resumption Techniques
Non-Interactive Mode Resumption
For automated workflows or scripts, you can use non-interactive mode to resume a conversation and perform a specific task:
# Resume last conversation in non-interactive mode
echo "Generate unit tests for the newly implemented WebSocket handler" | claude --continue --non-interactive > tests.txt
This resumes the most recent conversation, sends the specified message, and saves Claude's response to a file without requiring user interaction. It's perfect for creating automated scripts that generate code or documentation based on previous context.
Resumption with File Updates
When you've made changes to the codebase between sessions, it's good practice to update Claude about these changes:
claude --continue
# In Claude Code session:
Human: I've made some updates to the WebSocket implementation since our last session. Let me show you the current server.js file and then we can continue with the OT implementation.
Claude will use the tools available to examine updated files, ensuring it has the most current understanding of your codebase before continuing the work.
Combining Resumption with Git Worktrees
A powerful workflow combines Git worktrees from the previous tutorial with conversation resumption:
# In worktree A, working on feature X
cd ~/projects/myapp-feature-x
claude
# Later, resume this conversation for feature X
cd ~/projects/myapp-feature-x
claude --continue
# In worktree B, working on feature Y
cd ~/projects/myapp-feature-y
claude
# Later, resume conversation for feature Y
cd ~/projects/myapp-feature-y
claude --continue
This approach gives you dedicated Claude Code sessions for each feature, with each conversation maintaining its own context and history. Combined with Git worktrees, this creates a powerful environment for parallel feature development.
Best Practices for Conversation Maintenance
- Use regular checkpoints: Periodically summarize the current state of the project to create explicit reference points in the conversation history
- Maintain clear project boundaries: If working on multiple components within a project, clearly delineate when you're switching focus
- Update Claude on external changes: If you've made changes outside your conversations with Claude, provide updates at the beginning of resumed sessions
- End sessions with next steps: Conclude each session with a clear outline of what will be addressed next
- Use conversation branches when needed: For significant architectural changes, consider starting a new conversation branch rather than continuing an existing one
- Clean up unneeded conversations: Use the conversation history management tools to remove conversations that are no longer relevant
Troubleshooting Conversation Resumption
Issue 1: Context window limitations
For very long conversations, Claude might eventually reach memory limitations, particularly when dealing with large codebases or extensive discussions.
Solution:
When this happens, create periodic summary conversations that capture the essence of what's been done so far:
Human: We've been working on this collaborative editor for several days now. Could you create a comprehensive summary of what we've built so far, including the key architectural decisions and implementation details? I'll use this as a reference point for future sessions.
Save Claude's summary to a file and start a new conversation with this summary as context.
Issue 2: Lost or corrupted conversation history
Sometimes conversation history can be lost due to technical issues or file system problems.
Solution:
Maintain backup summaries of key architectural decisions and implementations, especially at the end of major development milestones:
# Export a conversation summary to a file
claude --continue --non-interactive << EOF > project_summary.md
Please create a comprehensive markdown document that summarizes our collaborative editor project so far. Include:
1. The overall architecture and components
2. Key implementation details for each component
3. Current status and what's left to implement
4. Any major decisions or trade-offs we've made
EOF
Issue 3: Multiple developers working on the same conversation
When multiple team members need to continue the same conversation, coordinating access can be challenging.
Solution:
Implement a handoff protocol where developers explicitly summarize the state for other team members:
# At the end of your session
Human: I'm going to hand off this conversation to my teammate Alex. Please summarize our current status and next steps in a format that will be clear for them to continue this work.
Conclusion
Claude Code's conversation resumption feature transforms how you approach complex, long-running software projects. By maintaining context across multiple development sessions, you can achieve greater consistency in architecture and implementation while reducing the cognitive overhead of context switching.
The techniques we've explored—from basic resumption to multi-day development patterns—enable Claude to function as a persistent development partner with an evolving understanding of your codebase and project goals. This leads to more coherent implementations, faster development velocity, and better architectural consistency across features.
In the next tutorial, we'll explore how to efficiently understand new codebases with Claude Code, building on the conversation management techniques we've established here.
Further Resources
Additional resources to deepen your understanding of working with Claude Code across extended development cycles:
Key Resources
Official Anthropic documentation for resuming conversations with Claude Code
Overview of Claude's memory and conversation capabilities
Best practices for using Claude in software development workflows
Understanding how context length affects Claude's capabilities and performance
Learn best practices for prompting Claude Code for development tasks
Comprehensive guide to how Claude manages conversation memory and context
Research on creating AI systems that maintain context over extended interactions
Understanding how context length affects Claude's capabilities and performance
Learn best practices for prompting Claude Code for development tasks