Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugins/orchestrator/dist/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22611,6 +22611,7 @@ function getDb(stateDir) {
db.exec("PRAGMA journal_mode = WAL;");
}
db.exec("PRAGMA synchronous = NORMAL;");
db.run("PRAGMA busy_timeout = 5000;");
db.exec(`
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
Expand Down
12 changes: 12 additions & 0 deletions plugins/orchestrator/mcp/engine/agent_channel_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,18 @@ function getDb(stateDir: string): Database {
db.exec("PRAGMA journal_mode = WAL;");
}
db.exec("PRAGMA synchronous = NORMAL;");
// WAL allows concurrent readers but writers serialize. Without a busy
// timeout, a concurrent writer throws SQLITE_BUSY immediately instead of
// waiting. Multiple Claude Code sessions in the same project all run their
// own MCP server with its own AgentChannel writing heartbeats, offsets,
// sessions, and system_events to this DB - and they all hit the stop-hook
// write path at end-of-session. Without this timeout, concurrent stop-hooks
// race for the writer lock, the loser sees SQLITE_BUSY immediately, Claude
// Code re-fires the stop-hook reminder, and the loser retries against the
// still-locked DB - producing the deadlock-shape that hangs both parent
// shells until host restart. Mirrors the same fix already applied to the
// global plugin DB at mcp/db/connection.ts.
db.run("PRAGMA busy_timeout = 5000;");
db.exec(`
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
Expand Down