What
sessions is created by lumen_core::schema::DDL with columns session_id, cwd, model, started_at, source_file. Nothing writes to it and nothing reads its contents. It holds 0 rows while turns holds 66 distinct session_id values, so the data it was meant to hold exists and simply never lands there. Nothing is broken today precisely because nothing reads it — that is the trap. The next person to touch metering finds a table that looks authoritative and either trusts it (and reads zeros) or spends time working out why it is empty.
Two tests do depend on the table existing, so dropping it is not a one-line change: both assert that a freshly created database has exactly four tables, sessions among them.
Evidence
sqlite3 "$(cat ~/.lumen_db_path)" "select count(*) from sessions" returns 0.
select count(distinct session_id) from turns on the same database returns 66.
- DDL:
crates/lumen-core/src/schema.rs:141 (CREATE TABLE IF NOT EXISTS sessions).
git grep -ni "into sessions" and git grep -ni "from sessions" both return nothing: no writer, no reader, in any crate, hook or query.
- Existence is depended on, contents are not:
crates/lumen-core/src/meter.rs:503-517 (connect_db_creates_the_file_and_the_schema) and crates/lumen-core/tests/integration.rs:16-26 (connect_db_creates_all_tables) each assert four tables in sqlite_master for ('turns','sessions','calibration','read_events').
- What already fills the gap:
get_sessions at crates/lumen-stats/src/lib.rs:259 builds one row per session with FROM turns t GROUP BY session_id, including a per-session model.
Acceptance criteria
There is a real decision here, and neither option is clearly right. Session-level metadata would be useful for per-project reporting, which argues for populating it — though model is already derived per session by get_sessions, so cwd is the only column that is genuinely unavailable today. Populating also adds a write to the metering path and a second source of truth for something already derivable from turns. Pick one and record the reason.
How to test
DB="$(cat ~/.lumen_db_path)"
# 1. Counts: 0 vs 66 today; equal after the populate fix.
sqlite3 "$DB" "select count(*) from sessions;"
sqlite3 "$DB" "select count(distinct session_id) from turns;"
# 2. Writer/reader search. Empty today. git grep, not grep -r:
# the working tree carries a multi-gigabyte target/.
git grep -ni "into sessions"
git grep -ni "from sessions"
# 3. Does the table exist? One row today.
sqlite3 "$DB" "select name from sqlite_master
where type='table' and name='sessions';"
# 4. Fresh database, without touching the live one. Do NOT rm "$DB":
# it is in WAL mode, so a plain cp is not a complete backup and
# the -wal/-shm sidecars would be left orphaned. Point LUMEN_DB
# (first-priority override) at a throwaway path instead, run one
# Claude Code turn, then repeat step 3 against it.
export LUMEN_DB=/tmp/lumen-fresh.db && rm -f /tmp/lumen-fresh.db*
# The same ground is covered without a live session by the two tests
# that build a fresh database in a temp dir and count its tables:
cargo test -p lumen-core connect_db_creates_the_file_and_the_schema
cargo test -p lumen-core --test integration connect_db_creates_all_tables
Expected after the fix: step 3 returns sessions and step 1 prints two equal numbers, or step 3 returns nothing on both the fresh and the pre-existing database with no migration error and the two table-count tests updated to match.
What
sessionsis created bylumen_core::schema::DDLwith columnssession_id, cwd, model, started_at, source_file. Nothing writes to it and nothing reads its contents. It holds 0 rows whileturnsholds 66 distinctsession_idvalues, so the data it was meant to hold exists and simply never lands there. Nothing is broken today precisely because nothing reads it — that is the trap. The next person to touch metering finds a table that looks authoritative and either trusts it (and reads zeros) or spends time working out why it is empty.Two tests do depend on the table existing, so dropping it is not a one-line change: both assert that a freshly created database has exactly four tables,
sessionsamong them.Evidence
sqlite3 "$(cat ~/.lumen_db_path)" "select count(*) from sessions"returns 0.select count(distinct session_id) from turnson the same database returns 66.crates/lumen-core/src/schema.rs:141(CREATE TABLE IF NOT EXISTS sessions).git grep -ni "into sessions"andgit grep -ni "from sessions"both return nothing: no writer, no reader, in any crate, hook or query.crates/lumen-core/src/meter.rs:503-517(connect_db_creates_the_file_and_the_schema) andcrates/lumen-core/tests/integration.rs:16-26(connect_db_creates_all_tables) each assert four tables insqlite_masterfor('turns','sessions','calibration','read_events').get_sessionsatcrates/lumen-stats/src/lib.rs:259builds one row per session withFROM turns t GROUP BY session_id, including a per-sessionmodel.Acceptance criteria
There is a real decision here, and neither option is clearly right. Session-level metadata would be useful for per-project reporting, which argues for populating it — though
modelis already derived per session byget_sessions, socwdis the only column that is genuinely unavailable today. Populating also adds a write to the metering path and a second source of truth for something already derivable fromturns. Pick one and record the reason.sessionsis written from the same placeturnsis written, andcount(*) from sessionsequalscount(distinct session_id) from turns.cwdandmodelare non-null for every row.sessionsis absent from the DDL and from a freshly created database.connect_db_creates_the_file_and_the_schemaandconnect_db_creates_all_tablesare updated to expect three tables, and both pass.into sessions/from sessionseither returns real writer and reader sites, or the table no longer exists.How to test
Expected after the fix: step 3 returns
sessionsand step 1 prints two equal numbers, or step 3 returns nothing on both the fresh and the pre-existing database with no migration error and the two table-count tests updated to match.