Skip to content

Bug: Pruned tool IDs are lost when navigating to old sessions (state not persisted) #16

@Tarquinen

Description

@Tarquinen

Summary

When a user navigates to a previous/old session in OpenCode, the DCP plugin loses all knowledge of which tool calls were previously pruned. This causes duplicated context replacements to fail and potentially re-pruning decisions that are incorrect because the plugin has no memory of its prior pruning decisions for that session.

Root Cause Analysis

Current Implementation (In-Memory Only)

The plugin stores pruned tool IDs in in-memory Map structures that are initialized when the plugin loads:

// index.ts:37-40
const prunedIdsState = new Map<string, string[]>()
const statsState = new Map<string, SessionStats>()
const toolParametersCache = new Map<string, any>() // callID -> parameters
const modelCache = new Map<string, { providerID: string; modelID: string }>()

This state only persists for the lifetime of the current OpenCode process. When:

  1. OpenCode is restarted, OR
  2. A user navigates to a different session and then back

...all pruning history for that session is lost.

The Fetch Wrapper Problem

The global fetch wrapper (index.ts:72-142) is designed to replace pruned tool outputs before they're sent to the LLM:

// Collect all pruned IDs across all sessions
const allPrunedIds = new Set<string>()
for (const session of allSessions.data) {
    if (session.parentID) continue
    const prunedIds = prunedIdsState.get(session.id) ?? []  // ← Returns empty array for restored sessions!
    prunedIds.forEach((id: string) => allPrunedIds.add(id))
}

When a user returns to an old session, prunedIdsState.get(session.id) returns undefined (defaulting to []), so no tool outputs are replaced.

Impact on Conversation Flow

  1. Token waste: Previously pruned tool outputs are sent back to the LLM in full
  2. Re-pruning chaos: The janitor may re-analyze tools it already pruned, potentially making different decisions
  3. Inconsistent context: Some tool outputs may have been replaced with [Output removed...] in the user's view but the LLM sees full content (or vice versa)

Reproduction Steps

  1. Start OpenCode and create a new session
  2. Perform several tool calls (reads, greps, etc.)
  3. Wait for DCP to prune some tools (you'll see the notification)
  4. Note which tools were pruned
  5. Navigate to a different session (or restart OpenCode)
  6. Navigate back to the original session
  7. Continue the conversation

Expected: Previously pruned tool outputs remain replaced with placeholder text
Actual: Full tool outputs are sent to the LLM, wasting tokens

Proposed Solution

DCP needs to persist pruned tool IDs to disk. Options:

Option 1: Session-scoped state file (Recommended)

Store state in a file per session:

~/.config/opencode/dcp-state/{sessionID}.json

Contents:

{
  "prunedIds": ["call_abc123", "call_def456"],
  "stats": { "totalToolsPruned": 5, "totalTokensSaved": 12000 },
  "lastUpdated": "2025-01-15T10:30:00Z"
}

Pros: Simple, isolated per session, easy to clean up
Cons: Requires state restoration on session load

Option 2: OpenCode storage API (if available)

Use OpenCode's plugin storage API if one exists to store arbitrary plugin state per session.

Option 3: Embed in session messages

Store pruned IDs in a hidden/ignored message in the session itself. This has the advantage of traveling with the session.

Additional Considerations

  1. Janitor context sanitization: The replacePrunedToolOutputs method in janitor.ts:395-423 also relies on the in-memory state. Without persistence, the janitor sends already-pruned content back to the analysis LLM.

  2. State migration: Need to handle:

    • Sessions created before state persistence was added
    • Corrupted state files
    • State file cleanup when sessions are deleted
  3. Thread safety: Multiple DCP instances (multiple OpenCode windows) could race on state updates.

Environment

  • DCP Version: Check package.json
  • OpenCode Version: Latest
  • OS: Linux

Related Code Locations

  • State initialization: index.ts:37-40
  • Fetch wrapper using state: index.ts:86-93
  • Janitor state update: janitor.ts:308-309
  • Janitor context sanitization: janitor.ts:395-423

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