fix: read OpenCode v1.2+ sessions from SQLite database#397
fix: read OpenCode v1.2+ sessions from SQLite database#397pedramamini wants to merge 1 commit intomainfrom
Conversation
OpenCode v1.2+ moved session storage from JSON files to SQLite at ~/.local/share/opencode/opencode.db. This caused Maestro to only show old sessions that still had JSON files on disk. - Add SQLite reading via better-sqlite3 (already a dependency) - Try SQLite first, fall back to JSON for pre-v1.2 installs - Merge and deduplicate sessions when both sources exist - Block deletion for SQLite-backed sessions (read-only access) - Add 12 tests covering SQLite, JSON fallback, and dedup scenarios
PR Review: fix: read OpenCode v1.2+ sessions from SQLite databaseOverall this is a well-structured fix for a real user-facing regression. The hybrid fallback strategy is the right approach for a migration period. Below are my findings organized by severity. Bugs / Correctness Issues1. DB connection leak in At line ~1009, when // Current code (leaks connection):
if (messageRows.length === 0) return null; // ← jumps past finally
// Fix: assign and fall through, or close explicitly before returning
if (messageRows.length === 0) {
db.close(); // or let the finally handle it by not returning early
return null;
}Wait — actually private listSessionsSqlite(projectPath: string): AgentSessionInfo[] | null {
const db = openOpenCodeDb();
if (!db) return null;
try {
if (!tableExists(db, 'session') || !tableExists(db, 'project')) {
return null; // ← db is never closed here
}
...
} finally {
db.close();
}
}This early return on schema check leaks the DB file handle. Move the 2. Null-dereference risk in
loaded = this.loadSessionMessagesSqlite(sessionId);
if (!loaded) {
loaded = await this.loadSessionMessages(sessionId);
}
const { messages, parts } = loaded; // ← loaded could still be null if JSON also fails?
3. WAL pragma on a read-only database In db.pragma('journal_mode = WAL');Setting 4. SQL injection via template literal in const placeholders = matchingProjectIds.map(() => '?').join(',');
const sessions = db
.prepare(`SELECT ... FROM session WHERE project_id IN (${placeholders}) ...`)
.all(...matchingProjectIds);
5. const sqliteResult = this.loadSessionMessagesSqlite(sessionId);
if (sqliteResult && sqliteResult.messages.length > 0) {
return { success: false, error: '...' };
}This opens and fully queries the DB (loading all messages and parts for the session) purely to determine if deletion is blocked. A lighter check — just verifying whether the DB file exists and the session row exists — would be more efficient: // Cheaper check: does the session exist in SQLite?
if (fsSync.existsSync(OPENCODE_DB_PATH)) {
const db = openOpenCodeDb();
if (db) {
try {
const row = db.prepare('SELECT id FROM session WHERE id = ?').get(sessionId);
if (row) return { success: false, error: '...' };
} finally {
db.close();
}
}
}Design / Architecture Notes6. DB opened and closed on every call — consider a cached connection or connection pool
7. Both const sqliteSessions = this.listSessionsSqlite(projectPath);
const jsonSessions = await this.listSessionsJson(projectPath);Both are always evaluated (SQLite synchronously, JSON asynchronously). On a post-migration v1.2+ install with no JSON files, 8. if (fsSync.existsSync(OPENCODE_DB_PATH)) {
return OPENCODE_DB_PATH; // same path regardless of sessionId
}If any consumer of Test Coverage9. Missing test: There's a test for SQLite message reading and one for delete rejection, but no test verifying that 10. Missing test: empty The "fall back to sessions with matching 11. Mock In the deduplication test (line ~354 in the test file), Minor / Style
Summary
The core approach is solid and the SQLite schema mapping looks correct against the OpenCode Drizzle schema. Address the DB leak and WAL pragma before merging; the rest are improvements rather than blockers. |
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
Summary
Fixes #387 — OpenCode v1.2+ moved session storage from JSON files (
~/.local/share/opencode/storage/) to SQLite (~/.local/share/opencode/opencode.db). Maestro only read JSON files, so sessions created after upgrading to v1.2 were invisible while older migrated sessions (with JSON files still on disk) continued to appear.better-sqlite3(already a project dependency) — queriesproject,session,message, andparttables matching OpenCode's v1.2+ Drizzle ORM schemadeleteMessagePairfor SQLite-backed sessionsNeeds validation
Test plan