DEV Community

pepk
pepk

Posted on

Fixing Claude Code's Concurrent Session Problem: Implementing Memory MCP with SQLite WAL Mode

The Problem

Have you ever seen this error while running multiple Claude Code sessions?

Error: database is locked
Enter fullscreen mode Exit fullscreen mode

Yeah, that was my first reaction too: "Wait, seriously?"

Memory MCP is an incredibly powerful tool for sharing knowledge across Claude Code sessions, but the official implementation doesn't support concurrent access. So I built a version using SQLite's Write-Ahead Logging (WAL) mode to solve this problem once and for all.

Who This Is For

  • Developers using Claude Code in their daily workflow
  • Anyone interested in Model Context Protocol (MCP)
  • Teams wanting to run multiple AI sessions in parallel
  • Developers looking for practical SQLite implementation patterns

The Official Memory MCP's Limitations

The official Memory MCP (@modelcontextprotocol/server-memory) stores knowledge graphs in JSONL files. This approach has some serious limitations:

1. No Concurrent Access Support

// Simplified view of the official implementation
const data = await fs.readFile('memory.jsonl', 'utf-8');
// ... process data ...
await fs.writeFile('memory.jsonl', newData);
Enter fullscreen mode Exit fullscreen mode

Without file locking mechanisms, concurrent writes from multiple sessions can corrupt your data. Not ideal.

2. Performance Degradation

The implementation reads all data into memory and performs linear searches. As your knowledge graph grows, queries get slower and slower.

3. No Transaction Support

If a partial update fails, there's no guarantee of data consistency. This is a dealbreaker for production use.

The Solution: SQLite + WAL Mode

I implemented a SQLite-based Memory MCP that solves all these issues.

What Is WAL (Write-Ahead Logging) Mode?

SQLite's WAL mode is genuinely game-changing:

  • Concurrent reads and writes: Multiple readers and one writer can access the database simultaneously
  • Crash recovery: Data integrity is guaranteed even if the process crashes mid-write
  • Faster writes: Log-based approach outperforms traditional rollback journals

It's one of those features that seems simple but changes everything.

Implementation Details

Database Initialization

import Database from 'better-sqlite3';
import path from 'path';
import fs from 'fs';

export class KnowledgeGraphStore {
  private db: Database.Database;

  constructor(dbPath: string) {
    // Create directory if it doesn't exist
    const dir = path.dirname(dbPath);
    if (dir && dir !== '.') {
      fs.mkdirSync(dir, { recursive: true });
    }

    this.db = new Database(dbPath);

    // Enable WAL mode for concurrent access
    this.db.pragma('journal_mode = WAL');

    // Set busy timeout to wait 5 seconds on lock contention
    this.db.pragma('busy_timeout = 5000');

    this.initSchema();
  }
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • journal_mode = WAL: Enables concurrent read/write access
  • busy_timeout = 5000: Waits 5 seconds before throwing a lock error

Without the busy timeout, you'll get immediate lock errors. Trust me, set this.

Schema Design

I structured the knowledge graph using three tables for efficient management:

private initSchema(): void {
  this.db.exec(`
    -- Entities (concepts) management
    CREATE TABLE IF NOT EXISTS entities (
      name TEXT PRIMARY KEY,
      entity_type TEXT NOT NULL
    );

    -- Observations and facts about entities
    CREATE TABLE IF NOT EXISTS observations (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      entity_name TEXT NOT NULL,
      content TEXT NOT NULL,
      FOREIGN KEY (entity_name) REFERENCES entities(name) ON DELETE CASCADE,
      UNIQUE(entity_name, content)  -- Prevent duplicates
    );

    -- Relationships between entities
    CREATE TABLE IF NOT EXISTS relations (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      from_entity TEXT NOT NULL,
      to_entity TEXT NOT NULL,
      relation_type TEXT NOT NULL,
      FOREIGN KEY (from_entity) REFERENCES entities(name) ON DELETE CASCADE,
      FOREIGN KEY (to_entity) REFERENCES entities(name) ON DELETE CASCADE,
      UNIQUE(from_entity, to_entity, relation_type)  -- Prevent duplicates
    );

    -- Indexes for performance optimization
    CREATE INDEX IF NOT EXISTS idx_observations_entity ON observations(entity_name);
    CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity);
    CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
  `);
}
Enter fullscreen mode Exit fullscreen mode

Design principles:

  • CASCADE: Automatically delete related data when an entity is removed (no orphaned records)
  • UNIQUE constraints: Prevent duplicate data
  • Indexes: Dramatically improve query performance

Those indexes are crucial. Without them, queries slow down fast.

Transaction-Based Updates

createEntities(entities: Entity[]): Entity[] {
  const insertEntity = this.db.prepare(
    'INSERT OR IGNORE INTO entities (name, entity_type) VALUES (?, ?)'
  );
  const insertObservation = this.db.prepare(
    'INSERT OR IGNORE INTO observations (entity_name, content) VALUES (?, ?)'
  );

  const created: Entity[] = [];

  // Process everything in a transaction
  const transaction = this.db.transaction((entities: Entity[]) => {
    for (const entity of entities) {
      insertEntity.run(entity.name, entity.entityType);
      for (const obs of entity.observations) {
        insertObservation.run(entity.name, obs);
      }
      created.push(entity);
    }
  });

  transaction(entities);
  return created;
}
Enter fullscreen mode Exit fullscreen mode

Why transactions matter:

  • Atomicity: Failed operations don't leave partial changes
  • Consistency: Multi-table updates maintain referential integrity
  • Performance: Batch commits are significantly faster

Without transactions, you can end up with corrupted data on errors. I learned this the hard way.

Published as an npm Package

I published this as an npm package so anyone can use it easily.

Installation

npm install @pepk/mcp-memory-sqlite
Enter fullscreen mode Exit fullscreen mode

Configuration for Claude Desktop / Claude Code

Add this to your ~/.claude.json (or global config):

{
  "mcpServers": {
    "memory": {
      "command": "npx",
      "args": ["@pepk/mcp-memory-sqlite"],
      "env": {
        "MEMORY_DB_PATH": "./.claude/memory.db"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Configuration tips:

  • MEMORY_DB_PATH: I recommend per-project databases like ./.claude/memory.db
  • Global sharing is possible: set to ~/memory.db or similar

Per-project databases are easier to manage in practice.

Comparison with Official Implementation

Feature Official (JSONL) This Implementation (SQLite + WAL)
Concurrent access ❌ Not supported ✅ Supported (WAL)
Transactions ❌ None ✅ ACID guarantees
Search speed Slow (linear) Fast (indexed)
Data integrity Weak Strong (foreign keys)
Crash recovery ❌ Manual repair ✅ Automatic (WAL)

The differences are pretty significant.

Troubleshooting

Error: database is locked

If you still get this with WAL mode enabled:

// Increase the busy timeout
this.db.pragma('busy_timeout = 10000'); // 10 seconds
Enter fullscreen mode Exit fullscreen mode

If 5 seconds isn't enough, try 10.

WAL File Growing Too Large

# Run checkpoint to truncate WAL file
sqlite3 memory.db "PRAGMA wal_checkpoint(TRUNCATE);"
Enter fullscreen mode Exit fullscreen mode

Running this periodically keeps the WAL file size under control.

Summary

I built a Memory MCP implementation using SQLite's WAL mode to solve concurrent session problems.

Key takeaways:

  • WAL mode enables concurrent read/write access
  • Transactions guarantee data consistency
  • Indexes provide fast search performance
  • Published as an npm package for easy use

The WAL mode's concurrent access support is the real game-changer here. Now you can safely run multiple sessions without worrying about data corruption.

Resources


If this helps solve your Claude Code concurrent session problems, I'd love to hear about it! Issues and PRs are always welcome.

Read in Other Languages

About the Author

Daichi Kudo

  • CEO at Cognisant LLC - Building the future where humans and AI create together
  • CTO at M16 LLC - AI, Creative & Engineering

Top comments (0)