Skip to main content
Glama
DrumRobot
by DrumRobot

split_session

Split Claude Code conversation sessions at a specific message to create new sessions, organizing discussions by topic or project phase.

Instructions

Split a session at a specific message, creating a new session with messages from that point onwards

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_nameYesProject folder name
session_idYesSession ID to split
message_uuidYesUUID of the message where the split starts (this message becomes the first message of the new session)

Implementation Reference

  • The main handler function that executes the split_session tool logic. It reads the session file, finds the split point by message UUID, handles special cases like continuation summaries by duplicating them, creates a new session file with subsequent messages, updates parentUuid chains, and migrates related agent files.
    export const splitSession = (projectName: string, sessionId: string, splitAtMessageUuid: string) =>
      Effect.gen(function* () {
        const projectPath = path.join(getSessionsDir(), projectName)
        const filePath = path.join(projectPath, `${sessionId}.jsonl`)
        const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))
        const lines = content.trim().split('\n').filter(Boolean)
    
        // Parse all messages preserving their full structure
        const allMessages = lines.map((line) => JSON.parse(line) as Record<string, unknown>)
    
        // Find the split point
        const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid)
        if (splitIndex === -1) {
          return { success: false, error: 'Message not found' } satisfies SplitSessionResult
        }
    
        if (splitIndex === 0) {
          return { success: false, error: 'Cannot split at first message' } satisfies SplitSessionResult
        }
    
        // Generate new session ID
        const newSessionId = crypto.randomUUID()
    
        // Check if the split message is a continuation summary
        const splitMessage = allMessages[splitIndex]
        const shouldDuplicate = isContinuationSummary(splitMessage)
    
        // Split messages - if continuation summary, include it in both sessions
        let remainingMessages: Record<string, unknown>[]
        const movedMessages = allMessages.slice(splitIndex)
    
        if (shouldDuplicate) {
          // Create a copy of the continuation message with new UUID for the original session
          const duplicatedMessage: Record<string, unknown> = {
            ...splitMessage,
            uuid: crypto.randomUUID(),
            sessionId: sessionId, // Keep original session ID
          }
          remainingMessages = [...allMessages.slice(0, splitIndex), duplicatedMessage]
        } else {
          remainingMessages = allMessages.slice(0, splitIndex)
        }
    
        // Update moved messages with new sessionId and fix first message's parentUuid
        const updatedMovedMessages = movedMessages.map((msg, index) => {
          const updated: Record<string, unknown> = { ...msg, sessionId: newSessionId }
          if (index === 0) {
            // First message of new session should have no parent
            updated.parentUuid = null
          }
          return updated
        })
    
        // Write remaining messages to original file
        const remainingContent = remainingMessages.map((m) => JSON.stringify(m)).join('\n') + '\n'
        yield* Effect.tryPromise(() => fs.writeFile(filePath, remainingContent, 'utf-8'))
    
        // Write moved messages to new session file
        const newFilePath = path.join(projectPath, `${newSessionId}.jsonl`)
        const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join('\n') + '\n'
        yield* Effect.tryPromise(() => fs.writeFile(newFilePath, newContent, 'utf-8'))
    
        // Update linked agent files that reference the old sessionId
        const agentFiles = yield* Effect.tryPromise(() => fs.readdir(projectPath))
        const agentJsonlFiles = agentFiles.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))
    
        for (const agentFile of agentJsonlFiles) {
          const agentPath = path.join(projectPath, agentFile)
          const agentContent = yield* Effect.tryPromise(() => fs.readFile(agentPath, 'utf-8'))
          const agentLines = agentContent.trim().split('\n').filter(Boolean)
    
          if (agentLines.length === 0) continue
    
          const firstAgentMsg = JSON.parse(agentLines[0]) as { sessionId?: string }
    
          // If this agent belongs to the original session, check if it should be moved
          if (firstAgentMsg.sessionId === sessionId) {
            // Check if any message in moved messages is related to this agent
            const agentId = agentFile.replace('agent-', '').replace('.jsonl', '')
            const isRelatedToMoved = movedMessages.some(
              (msg) => (msg as { agentId?: string }).agentId === agentId
            )
    
            if (isRelatedToMoved) {
              // Update all messages in this agent file to reference new sessionId
              const updatedAgentMessages = agentLines.map((line) => {
                const msg = JSON.parse(line) as Record<string, unknown>
                return JSON.stringify({ ...msg, sessionId: newSessionId })
              })
              const updatedAgentContent = updatedAgentMessages.join('\n') + '\n'
              yield* Effect.tryPromise(() => fs.writeFile(agentPath, updatedAgentContent, 'utf-8'))
            }
          }
        }
    
        return {
          success: true,
          newSessionId,
          newSessionPath: newFilePath,
          movedMessageCount: movedMessages.length,
          duplicatedSummary: shouldDuplicate,
        } satisfies SplitSessionResult
      })
  • Output type definition for the splitSession function, used in the tool response.
    export interface SplitSessionResult {
      success: boolean
      newSessionId?: string
      newSessionPath?: string
      movedMessageCount?: number
      duplicatedSummary?: boolean
      error?: string
    }
  • MCP server tool registration for 'split_session', defining input schema with Zod and calling the splitSession handler.
    server.tool(
      'split_session',
      'Split a session at a specific message, creating a new session with messages from that point onwards',
      {
        project_name: z.string().describe('Project folder name'),
        session_id: z.string().describe('Session ID to split'),
        message_uuid: z
          .string()
          .describe(
            'UUID of the message where the split starts (this message becomes the first message of the new session)'
          ),
      },
      async ({ project_name, session_id, message_uuid }) => {
        const result = await Effect.runPromise(
          session.splitSession(project_name, session_id, message_uuid)
        )
        return {
          content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
        }
      }
  • Helper utility to detect if a message is a continuation summary, which determines whether to duplicate it during split.
    const isContinuationSummary = (msg: Record<string, unknown>): boolean => {
      // isCompactSummary flag is set by Claude Code for continuation summaries
      if (msg.isCompactSummary === true) return true
    
      // Fallback: check message content
      if (msg.type !== 'user') return false
      const message = msg.message as { content?: string } | undefined
      const content = message?.content ?? ''
      return content.startsWith('This session is being continued from')
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DrumRobot/claude-sessions-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server