Skip to content

Conversation

@ammar-agent
Copy link
Collaborator

Problem

After running /compact -c "continue message", the continue message is sometimes sent multiple times in a row instead of just once.

Root Cause

The previous workspace-level guard had a race condition:

  1. Backend sends two events for replaceChatHistory: delete event, then new message event
  2. Each event causes immediate synchronous bump() which notifies all subscribers
  3. Multiple checkAutoCompact() calls can be in-flight simultaneously:
    • Delete event → bump() → subscriber fires → checkAutoCompact() starts
    • New message event → bump() → subscriber fires → checkAutoCompact() starts
    • Both calls check workspace guard before either sets it → both proceed to send

The double-check guard in PR #334 helped but didn't fully solve it because the checks and sets are separate operations.

The real issue: Workspace-level tracking is the wrong granularity. We need to prevent processing the same compaction MESSAGE multiple times, not the same workspace multiple times. A new compaction creates a new message with a new ID.

Solution

Track processed message IDs instead of workspace IDs:

// Track which specific compaction summary messages we've already processed
const processedMessageIds = useRef<Set<string>>(new Set());

// In checkAutoCompact:
const messageId = summaryMessage.id;

// Have we already processed this specific compaction message?
if (processedMessageIds.current.has(messageId)) continue;

// Mark THIS MESSAGE as processed
processedMessageIds.current.add(messageId);

Why This Is Obviously Correct

  1. Message IDs are unique and immutable - Once a message exists, its ID never changes
  2. We only care about processing each message once - Not about processing each workspace once
  3. The guard is set BEFORE the async operation - No timing window
  4. Multiple calls can overlap - But they all see the same message ID, so only the first one proceeds
  5. Cleanup is natural - IDs accumulate bounded (one per compaction) and don't need explicit cleanup

The correctness is self-evident: "Have we sent a continue message for THIS compaction result message? Yes/No."

How It Fixes The Race

Before (workspace-level):

After (message-level):

Both calls are checking the same unique identifier (the message ID), so the guard works correctly even with concurrent execution.

Testing

Manual testing needed:

  1. Run /compact -c "continue message" multiple times
  2. Verify only ONE continue message is sent per compaction
  3. Check console logs for single "Sending continue message" per compaction
  4. Verify backend receives only one user message (not duplicates)

Generated with cmux

The previous workspace-level guard had a race condition because
replaceChatHistory sends two events (delete, then new message),
each triggering immediate subscription callbacks. Multiple
checkAutoCompact() calls could be in-flight simultaneously, both
checking the guard before either set it.

Root cause: Workspace-level tracking is the wrong granularity.
We need to prevent processing the same compaction MESSAGE multiple
times, not the same workspace multiple times.

Fix: Track processed message IDs instead of workspace IDs.
- Message IDs are unique and immutable
- Multiple concurrent calls see the same message ID
- Only first call proceeds, others skip
- Correctness is obvious from implementation

Why this works:
- Delete event arrives → bump() → checkAutoCompact() starts
- New message event arrives → bump() → checkAutoCompact() starts
- Both calls extract the same summaryMessage.id
- Both check processedMessageIds.has(messageId)
- Only first call proceeds past the check and adds the ID
- Second call sees ID already in set, skips

The key insight: workspace can be compacted multiple times
(different messages), but each compaction message is unique.
Track what we've processed, not where.

Generated with cmux
@ammario ammario added this pull request to the merge queue Oct 19, 2025
Merged via the queue into main with commit af32a5b Oct 19, 2025
8 checks passed
@ammario ammario deleted the fix-compact-continue-race-messageid branch October 19, 2025 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants