Deleted Session Tombstone Semantics#
When a session is deleted, Decant performs a hard delete with tombstone protection: live session rows are physically removed, but a tombstone record in ingest_source is left behind to prevent a future sync from re-ingesting the same transcript.
What happens during deletion#
setSessionUserState(db, id, "deleted") calls deleteSessionIdentities(), which:
- Deletes the
sessionrows (and orphans children by clearingparent_session_id). - Updates
ingest_sourcefor any paths that no longer have surviving session rows:SET session_id = NULL, status = 'skipped_deleted'
Both steps happen atomically inside the same database write transaction. The source JSONL file is never modified.
Why the tombstone is unreachable by session ID#
After the update, ingest_source.session_id is NULL. Any query of the form:
SELECT * FROM ingest_source WHERE session_id = ?
returns no rows — the tombstone row exists, but the foreign key is gone. This is by design: the row's purpose is to block re-ingestion, not to be found by session lookup.
The only reliable way to reach the tombstone is to query by path:
SELECT * FROM ingest_source WHERE path = ? AND status = 'skipped_deleted'
This matters when writing tests or diagnostics that assert on tombstone presence: always key on path, never on a session ID.
The ingest_source schema confirms that path is the PRIMARY KEY and session_id is a nullable REFERENCES session(id) ON DELETE SET NULL.
Transcript bytes persist until vacuum#
Deleting rows in SQLite frees pages but does not zero their content. Deleted transcript text remains physically present in decant.db — and greppable — until the file is rewritten.
Run the following to reclaim that space:
decant db vacuum
This executes VACUUM; on the archive, rewriting the database file and eliminating freed pages. The CLI also surfaces a reminder after every session deletion.
Implication: documentation or comments that describe deletion as "removing the transcript" are only accurate if they also name vacuum. Until decant db vacuum runs, the transcript text is still present on disk.
Related: Claude spawn tombstones#
For Claude Code sessions, child transcripts spawned via tool calls get their own session_user_state tombstones under the __decant_internal_claude_spawn__ namespace. When a spawned child transcript is encountered during a later sync, inheritDeletedSessionTombstone() propagates the deletion without re-ingesting the transcript.
Key source locations#
| Concern | Location |
|---|---|
deleteSessionIdentities() — tombstone write | src/session-user-state.ts lines 307–342 |
setSessionUserState() — deletion entry point | src/session-user-state.ts lines 207–263 |
ingest_source schema | src/schema.sql lines 140–152 |
decant db vacuum command | src/cli.ts lines 719–731 |
| Session lifecycle overview | Session Lifecycle Management |