Skip to content

Conversation

@27Bslash6
Copy link

Summary

Fixes #2 - Implements bounded state management to prevent memory leaks in long-running server sessions.

The Problem

  • thoughtHistory and branches arrays grew unbounded
  • Long-running stdio processes accumulated state indefinitely
  • Memory leak in production usage over hours/days
  • Issue Create package for each server #2 identified this as CRITICAL

Why PR #2 Was Wrong

PR #2 attempted to fix this by creating per-request instances (stateless architecture). While that prevented memory leaks, it completely broke the intended functionality:

  • Thought history tracking across multiple tool calls stopped working
  • Branch management became impossible
  • Tests that verified multi-thought sessions passed only because they were synchronous

The singleton pattern is actually correct for stdio transport, where each client spawns a dedicated server process. The real issue was unbounded growth, not shared state.

The Solution

Maintain the singleton pattern (necessary for stdio) but add configurable bounds:

  1. MAX_THOUGHT_HISTORY (default: 1000) - Cap on thought history
  2. MAX_BRANCHES (default: 100) - Cap on branch tracking
  3. FIFO Eviction - Oldest entries removed when limits exceeded
  4. Environment Variable Configuration - Users can adjust limits

Changes

  • lib.ts: Add bounded state with FIFO cleanup (lines 15-21, 157-174)
  • lib.test.ts: Add 3 new tests verifying memory bounds enforcement
  • README.md: Document env vars and architecture design rationale
  • Reverted PR Create package for each server #2: Restored singleton pattern

Test Results

✓ __tests__/lib.test.ts (40 tests) 12ms
  ✓ processThought - memory bounds enforcement
    ✓ should enforce maximum history size with default 1000 limit
    ✓ should enforce maximum branches limit with default 100 limit
    ✓ should use FIFO eviction for thought history

 Test Files  1 passed (1)
      Tests  40 passed (40)

Architecture Notes

This server is designed for stdio transport where:

  • Each client spawns a dedicated process
  • The process lives for the entire session
  • Tool calls are sequential (LLM waits for response before next call)
  • Singleton provides necessary state persistence across calls

Concurrent requests are theoretically possible but extremely rare in practice (would require LLM explicitly calling tools in parallel).

Memory Impact

Before: Unbounded growth (1000 thoughts = ~1MB, could grow indefinitely)
After: Capped at ~100KB-1MB depending on configuration

Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com

Fixes #3

Problem:
- Falsy checks (!data.thoughtNumber) rejected valid 0 values
- No bounds checking for negative numbers, NaN, Infinity
- Unsafe type assertions on optional fields without validation
- Empty strings passed validation

Solution:
- Replace falsy checks with explicit typeof and bounds validation
- Add Number.isFinite() checks to reject NaN and Infinity
- Add explicit >= 1 checks for numeric fields
- Validate optional fields only when present (undefined check)
- Add length check for string fields

Tests:
- Added 12 new tests covering edge cases
- All 37 tests pass
- Coverage: 94.59% statements, 91.8% branches

This is the foundation for subsequent fixes - validation must be
rock-solid before addressing concurrency and state management.
…ditions

Previously, a single SequentialThinkingServer instance was shared across all requests (index.ts:128), causing:
- Race conditions in concurrent requests
- Session pollution between different conversations
- Incorrect thoughtHistoryLength values
- Non-deterministic behavior

Fix: Create new SequentialThinkingServer instance per request in CallToolRequestSchema handler. This ensures complete state isolation between requests.

Tests added:
- State isolation between different server instances
- Branch isolation (no cross-contamination)
- Interleaved concurrent request handling

All 40 tests pass. Zero state leakage.

Resolves #1
Implements bounded state management to prevent memory leaks in long-running server sessions.

Problem:
- thoughtHistory and branches arrays grow unbounded
- Long-running stdio processes accumulate state indefinitely
- Memory leak in production usage over hours/days

Solution:
- Add MAX_THOUGHT_HISTORY (default: 1000) for thought history cap
- Add MAX_BRANCHES (default: 100) for branch tracking cap
- Implement FIFO eviction when limits exceeded
- Both limits configurable via environment variables

Changes:
- lib.ts: Add bounded state with FIFO cleanup
- lib.test.ts: Add 3 tests verifying memory bounds enforcement
- README.md: Document env vars and architecture design

Architecture Notes:
PR #2 attempted to fix this by making instances per-request (stateless), but that broke the intended functionality of tracking thought history across multiple tool calls. The singleton pattern is correct for stdio transport where each client spawns a dedicated process. This fix maintains the singleton while preventing unbounded growth.

Tests: 40/40 pass (including 3 new memory bounds tests)
Resolves #2
@27Bslash6
Copy link
Author

Closing - meant for fork. Apologies.

@27Bslash6 27Bslash6 closed this Nov 15, 2025
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.

1 participant