Problem
ignoreSessionPatterns (added in #39) does not filter cron-spawned conversations in practice. All conversations ingested via the bootstrap path have session_key = NULL in the conversations table, so the glob pattern has nothing to match against.
Config:
{
"ignoreSessionPatterns": ["agent:*:cron:**"]
}
The patterns compile fine and the startup banner confirms they're loaded. But the filter never fires because the matching candidate is always empty.
Root cause
In src/engine.ts, shouldIgnoreSession() (line ~1084) prefers sessionKey and falls back to sessionId:
const candidate =
typeof params.sessionKey === "string" && params.sessionKey.trim()
? params.sessionKey.trim()
: (params.sessionId?.trim() ?? "");
The fallback to sessionId looks like it should work, but sessionId is a UUID (e.g., a3f1b2c4-...), not a structured key like agent:cron-doctor:cron:daily-audit. The glob pattern agent:*:cron:** will never match a UUID.
Meanwhile, in src/store/conversation-store.ts, createConversation() inserts sessionKey ?? null (line ~280). The upstream callers in the bootstrap/ingestion path don't populate sessionKey, so every conversation ends up with session_key = NULL in the database. The shouldIgnoreSession check runs before the conversation is stored, but since the caller doesn't pass a sessionKey either, the candidate resolves to the UUID, and the pattern never matches.
Evidence
After 10 days of operation on v0.5.1 with the config above:
sqlite3 lcm.db "SELECT COUNT(*) FROM conversations"
-- 5197
sqlite3 lcm.db "SELECT COUNT(*) FROM conversations WHERE session_key IS NOT NULL"
-- 0
-- Cron sessions identified by first message content:
sqlite3 lcm.db "
SELECT COUNT(DISTINCT c.conversation_id) FROM conversations c
JOIN messages m ON m.conversation_id = c.conversation_id AND m.seq = 1
WHERE m.content LIKE '%[cron:%'
"
-- 5052 (97% of all conversations)
5,052 cron sessions vs 145 interactive sessions. All of them with session_key = NULL. The filter was configured correctly but had zero effect.
Impact
The unfiltered cron sessions caused two compounding problems:
-
DB bloat: lcm.db grew to 890MB in 10 days, almost entirely cron conversation data.
-
Gateway startup hang: The migration in src/db/migration.ts runs backfillSummaryDepths(), backfillSummaryMetadata(), and backfillToolCallColumns() on every startup against the entire database (lines ~586-588). With 5,000+ conversations worth of summaries and message_parts, these backfills take long enough to hold the SQLite write lock. Concurrent processes (e.g., a memory-watchdog spawning openclaw CLI children) also try to acquire the lock for their own migration, creating a deadlock where neither process can complete. The gateway never finishes starting.
This is related to #150 (bootstrap stall) and may compound with #73 (concurrent compaction) and #160 (compaction timeouts) in setups where multiple OpenClaw processes share the same lcm.db.
Workaround
Identified cron conversations by their first message content and deleted them with their dependent rows in FK order:
-- Collect cron conversation IDs
CREATE TEMP TABLE cron_convos AS
SELECT DISTINCT c.conversation_id FROM conversations c
JOIN messages m ON m.conversation_id = c.conversation_id AND m.seq = 1
WHERE m.content LIKE '%[cron:%';
-- Delete dependents in FK order
DELETE FROM context_items WHERE conversation_id IN (SELECT conversation_id FROM cron_convos);
DELETE FROM summary_parents WHERE summary_id IN (
SELECT summary_id FROM summaries WHERE conversation_id IN (SELECT conversation_id FROM cron_convos));
DELETE FROM summary_messages WHERE summary_id IN (
SELECT summary_id FROM summaries WHERE conversation_id IN (SELECT conversation_id FROM cron_convos));
DELETE FROM summaries WHERE conversation_id IN (SELECT conversation_id FROM cron_convos);
DELETE FROM message_parts WHERE message_id IN (
SELECT message_id FROM messages WHERE conversation_id IN (SELECT conversation_id FROM cron_convos));
DELETE FROM messages WHERE conversation_id IN (SELECT conversation_id FROM cron_convos);
DELETE FROM large_files WHERE conversation_id IN (SELECT conversation_id FROM cron_convos);
DELETE FROM conversation_bootstrap_state WHERE conversation_id IN (SELECT conversation_id FROM cron_convos);
DELETE FROM conversations WHERE conversation_id IN (SELECT conversation_id FROM cron_convos);
-- Rebuild FTS
INSERT INTO messages_fts(messages_fts) VALUES('rebuild');
INSERT INTO summaries_fts(summaries_fts) VALUES('rebuild');
VACUUM;
Result: 890MB to 133MB. Gateway starts in ~2 seconds. Running this as a weekly cron to prevent recurrence.
Suggested fix
Two options (not mutually exclusive):
Option A: Populate session_key during ingestion. The structured session key (e.g., agent:cron-doctor:cron:daily-audit) exists somewhere in the OpenClaw session metadata. If the ingestion/bootstrap path passed it through to createConversation(), the existing matching logic in shouldIgnoreSession() would work as designed.
Option B: Match against session_id with a broader pattern set. Less ideal since UUIDs aren't human-readable, but as a fallback, the docs could clarify that ignoreSessionPatterns only works when session_key is populated, and provide guidance on how to ensure that happens.
Option A is the real fix. The matching logic and pattern compilation in src/session-patterns.ts are fine. The problem is purely that the data never reaches the filter.
Environment
- lossless-claw v0.5.1
- macOS (darwin arm64)
- SQLite (node:sqlite built-in)
Problem
ignoreSessionPatterns(added in #39) does not filter cron-spawned conversations in practice. All conversations ingested via the bootstrap path havesession_key = NULLin theconversationstable, so the glob pattern has nothing to match against.Config:
{ "ignoreSessionPatterns": ["agent:*:cron:**"] }The patterns compile fine and the startup banner confirms they're loaded. But the filter never fires because the matching candidate is always empty.
Root cause
In
src/engine.ts,shouldIgnoreSession()(line ~1084) preferssessionKeyand falls back tosessionId:The fallback to
sessionIdlooks like it should work, butsessionIdis a UUID (e.g.,a3f1b2c4-...), not a structured key likeagent:cron-doctor:cron:daily-audit. The glob patternagent:*:cron:**will never match a UUID.Meanwhile, in
src/store/conversation-store.ts,createConversation()insertssessionKey ?? null(line ~280). The upstream callers in the bootstrap/ingestion path don't populatesessionKey, so every conversation ends up withsession_key = NULLin the database. TheshouldIgnoreSessioncheck runs before the conversation is stored, but since the caller doesn't pass asessionKeyeither, the candidate resolves to the UUID, and the pattern never matches.Evidence
After 10 days of operation on v0.5.1 with the config above:
5,052 cron sessions vs 145 interactive sessions. All of them with
session_key = NULL. The filter was configured correctly but had zero effect.Impact
The unfiltered cron sessions caused two compounding problems:
DB bloat:
lcm.dbgrew to 890MB in 10 days, almost entirely cron conversation data.Gateway startup hang: The migration in
src/db/migration.tsrunsbackfillSummaryDepths(),backfillSummaryMetadata(), andbackfillToolCallColumns()on every startup against the entire database (lines ~586-588). With 5,000+ conversations worth of summaries and message_parts, these backfills take long enough to hold the SQLite write lock. Concurrent processes (e.g., a memory-watchdog spawningopenclawCLI children) also try to acquire the lock for their own migration, creating a deadlock where neither process can complete. The gateway never finishes starting.This is related to #150 (bootstrap stall) and may compound with #73 (concurrent compaction) and #160 (compaction timeouts) in setups where multiple OpenClaw processes share the same
lcm.db.Workaround
Identified cron conversations by their first message content and deleted them with their dependent rows in FK order:
Result: 890MB to 133MB. Gateway starts in ~2 seconds. Running this as a weekly cron to prevent recurrence.
Suggested fix
Two options (not mutually exclusive):
Option A: Populate
session_keyduring ingestion. The structured session key (e.g.,agent:cron-doctor:cron:daily-audit) exists somewhere in the OpenClaw session metadata. If the ingestion/bootstrap path passed it through tocreateConversation(), the existing matching logic inshouldIgnoreSession()would work as designed.Option B: Match against
session_idwith a broader pattern set. Less ideal since UUIDs aren't human-readable, but as a fallback, the docs could clarify thatignoreSessionPatternsonly works whensession_keyis populated, and provide guidance on how to ensure that happens.Option A is the real fix. The matching logic and pattern compilation in
src/session-patterns.tsare fine. The problem is purely that the data never reaches the filter.Environment