Worker-service compression appears to leak recent cross-session context into unrelated observations
Title
Worker-service compression appears to leak recent cross-session context into unrelated observations
Body
Summary
When replaying historical tool_use/tool_result pairs through the hook claude-code observation CLI command (to backfill observations for sessions that predate claude-mem, or that ran without live hooks), the resulting observation sometimes describes content that has nothing to do with the tool_input/tool_response actually sent in that hook call — instead, it matches whatever topic is currently being discussed in a different, concurrently-active session on the same machine.
Reproduction (3 independent trials, same result each time)
I wrote a small script that calls node bun-runner.js worker-service.cjs hook claude-code observation once per historical tool_use event, with a payload shaped exactly like the real PostToolUse hook JSON (session_id, cwd, hook_event_name: "PostToolUse", tool_name, tool_input, tool_response), followed by one hook claude-code summarize call. transcript_path is intentionally left empty since there is no live transcript for a historical replay.
Trial 1 — Replayed 6 tool_use events from a historical session about "CLAUDE.md duplicate-content review" (Agent dispatch + Write + AskUserQuestion). Used
session_id = "backfill-test-{realSessionUUID}-{epoch}"(reusing the real session's UUID with a tag prefix). Result: 1 observation was created, titled "Updated worker service configuration", describing "worker-service.cjs" and "settings.json" error-handling changes — content that does not appear anywhere in the 6 events I sent. It does, however, appear literally in the full original session's transcript file (confirmed viagrep -c), which was NOT part of my payload. → Hypothesis: with an emptytranscript_path, something resolves the real transcript file via the UUID embedded insession_idand pulls broader context from it.Trial 2 (after switching to a synthetic UUID with no relation to any real transcript file) — Same source session, but
session_idis now a freshuuid4()per import run ({tag}-{uuid4()}), guaranteeing no~/.claude/projects/*/{uuid}.jsonlfile exists that matches it. Result: still 1 fabricated observation, again about "worker-service.cjs"/"settings.json" missing-key logging — again unrelated to the actual 6 events (which were about CLAUDE.md review, confirmed by direct inspection of all 6tool_input/tool_responsepayloads).Trial 3 (control, different source session, still synthetic UUID) — Replayed 8 tool_use events from a completely different historical session (about resuming a video-production dev session / handoff notes — no relation to claude-mem internals at all). Result: again 1 fabricated observation, again titled around "worker service configuration" / "settings.json" missing-key handling.
Why I believe this is cross-session context leakage, not random hallucination
The Claude Code session I was running this entire investigation from was, at the time, itself actively discussing and reading worker-service.cjs and settings.json (I had just grep'd the real worker-service.cjs source for tool-type handling). That investigation session has claude-mem's real-time hooks active, so it generated its own genuine observation:
title: "worker-service.cjs에서 Agent 툴타입 특별 취급 확인"
created_at: 2026-09-16T09:18:50Z
~90 seconds later, my import call (running against a different, synthetic, unrelated session_id) produced:
title: "Updated worker service configuration"
narrative: "The worker now logs a warning when a required key is missing
from settings.json, helping to identify and handle the issue more
gracefully."
created_at: 2026-09-16T09:20:20Z
Same theme, ~90s apart, on the same machine, same live worker process (PID confirmed stable across all trials via /api/health). This repeated 3 times across genuinely unrelated source content, always matching whatever my live investigation session happened to be discussing at that moment — never the actual payload I sent for compression.
Hypothesis
The worker (a machine-wide singleton, not scoped per caller) appears to include some form of "recent activity" or "recent related observations" as scene-setting context when building the compression prompt for a hook ... observation call — and that context is not properly isolated per session_id. Under normal real-time hook usage (one active session at a time, hooks fired only for that session's own events) this would be invisible. It only surfaces when something sends the worker compression requests concurrently with — and topically overlapping — genuine live hook activity from another session.
Environment
- claude-mem version: 13.15.2 (from
/api/health) - Provider:
openrouterpointed at local Ollama, modelqwen2.5:3b(confirmed viagenerated_by_modelcolumn on the affected rows) - Windows 11, worker running as a persistent background process (not
managed)
What I'm not certain of
I don't have visibility into the (minified/bundled) compression prompt-building code, so I can't point to the exact line. I've only confirmed the symptom and its timing/topic correlation with concurrent live activity, not the mechanism inside worker-service.cjs. Happy to share the exact reproduction script if useful.
Suggested mitigation (workaround I've adopted, not a fix)
Anyone building an "import historical session into claude-mem" tool similar to mine should avoid running it from a live session that is itself actively discussing claude-mem internals — that's the condition that reliably triggers the leak in my testing. This is documented as a warning in my own tool now, but it would be good for claude-mem itself to scope compression context strictly to the session_id/payload the caller actually sent.
Source: thedotmack/claude-mem