Skip to content

ignoreSessionPatterns has no effect: cron conversations have session_key = NULL, so glob matching silently no-ops #176

@BryanTegomoh

Description

@BryanTegomoh

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:

  1. DB bloat: lcm.db grew to 890MB in 10 days, almost entirely cron conversation data.

  2. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions