fix(session-sync): handle missing estimated_cost_usd column in old Hermes state.db (#312)

* fix(session-sync): handle missing estimated_cost_usd column in old Hermes state.db

Fixes #308 - "NOT NULL constraint failed: sessions.estimated_cost_usd"

Problem:
- Old versions of Hermes state.db don't have the estimated_cost_usd column
- Session sync would fail when trying to query this column
- New sessions also failed to sync because the error blocked the entire sync process

Solution:
- Dynamically detect if estimated_cost_usd column exists using PRAGMA table_info
- For old DBs (no column): return 0 as hardcoded default value
- For new DBs (has column): use COALESCE(estimated_cost_usd, 0) to handle NULL values
- This ensures backward compatibility with both old and new Hermes installations

Changes:
- Add PRAGMA table_info check before building SELECT query
- Conditionally include estimated_cost_usd column based on schema detection
- Ensures session sync works for both old and new Hermes state.db versions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: correct type annotation for PRAGMA table_info result

- Change from Array<{ name: string }>[] to Array<{ name: string }>
- Fixes TypeScript compilation error
- PRAGMA table_info returns an array of objects, not an array of arrays

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-29 21:03:51 +08:00
committed by GitHub
parent 5c6699ab72
commit 3e6c96a896
@@ -112,6 +112,13 @@ function syncProfileSessions(profile: string): {
const db = openHermesStateDb(profile)
try {
// Check if sessions table has estimated_cost_usd column
const tableInfo = db.prepare('PRAGMA table_info(sessions)').all() as Array<{ name: string }>
const hasEstimatedCost = tableInfo.some(col => col.name === 'estimated_cost_usd')
// Build SELECT query - only include estimated_cost_usd if column exists
const estimatedCostCol = hasEstimatedCost ? ', COALESCE(estimated_cost_usd, 0) AS estimated_cost_usd' : ', 0 AS estimated_cost_usd'
// Get all api_server sessions
const sessions = db.prepare(`
SELECT
@@ -128,8 +135,7 @@ function syncProfileSessions(profile: string): {
output_tokens,
cache_read_tokens,
cache_write_tokens,
reasoning_tokens,
estimated_cost_usd
reasoning_tokens${estimatedCostCol}
FROM sessions
WHERE source = 'api_server'
ORDER BY started_at ASC
@@ -218,7 +224,7 @@ function syncProfileSessions(profile: string): {
cache_read_tokens: hermesSession.cache_read_tokens,
cache_write_tokens: hermesSession.cache_write_tokens,
reasoning_tokens: hermesSession.reasoning_tokens,
estimated_cost_usd: hermesSession.estimated_cost_usd,
estimated_cost_usd: hermesSession.estimated_cost_usd || 0,
last_active: hermesSession.started_at, // Use started_at as fallback since last_active doesn't exist in Hermes state.db
preview,
})