Files
claude-mem/docs/reports/issue-590-windows-chroma-terminal-popup.md
T
Alex Newman 2659ec3231 fix: Claude Code 2.1.1 compatibility + log-level audit + path validation fixes (#614)
* Refactor CLAUDE.md and related files for December 2025 updates

- Updated CLAUDE.md in src/services/worker with new entries for December 2025, including changes to Search.ts, GeminiAgent.ts, SDKAgent.ts, and SessionManager.ts.
- Revised CLAUDE.md in src/shared to reflect updates and new entries for December 2025, including paths.ts and worker-utils.ts.
- Modified hook-constants.ts to clarify exit codes and their behaviors.
- Added comprehensive hooks reference documentation for Claude Code, detailing usage, events, and examples.
- Created initial CLAUDE.md files in various directories to track recent activity.

* fix: Merge user-message-hook output into context-hook hookSpecificOutput

- Add footer message to additionalContext in context-hook.ts
- Remove user-message-hook from SessionStart hooks array
- Fixes issue where stderr+exit(1) approach was silently discarded

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update logs and documentation for recent plugin and worker service changes

- Added detailed logs for worker service activities from Dec 10, 2025 to Jan 7, 2026, including initialization patterns, cleanup confirmations, and diagnostic logging.
- Updated plugin documentation with recent activities, including plugin synchronization and configuration changes from Dec 3, 2025 to Jan 7, 2026.
- Enhanced the context hook and worker service logs to reflect improvements and fixes in the plugin architecture.
- Documented the migration and verification processes for the Claude memory system and its integration with the marketplace.

* Refactor hooks architecture and remove deprecated user-message-hook

- Updated hook configurations in CLAUDE.md and hooks.json to reflect changes in session start behavior.
- Removed user-message-hook functionality as it is no longer utilized in Claude Code 2.1.0; context is now injected silently.
- Enhanced context-hook to handle session context injection without user-visible messages.
- Cleaned up documentation across multiple files to align with the new hook structure and removed references to obsolete hooks.
- Adjusted timing and command execution for hooks to improve performance and reliability.

* fix: Address PR #610 review issues

- Replace USER_MESSAGE_ONLY test with BLOCKING_ERROR test in hook-constants.test.ts
- Standardize Claude Code 2.1.0 note wording across all three documentation files
- Exclude deprecated user-message-hook.ts from logger-usage-standards test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Remove hardcoded fake token counts from context injection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR #610 review issues by fixing test files, standardizing documentation notes, and verifying code quality improvements.

* fix: Add path validation to CLAUDE.md distribution to prevent invalid directory creation

- Add isValidPathForClaudeMd() function to reject invalid paths:
  - Tilde paths (~) that Node.js doesn't expand
  - URLs (http://, https://)
  - Paths with spaces (likely command text or PR references)
  - Paths with # (GitHub issue/PR references)
  - Relative paths that escape project boundary

- Integrate validation in updateFolderClaudeMdFiles loop
- Add 6 unit tests for path validation
- Update .gitignore to prevent accidental commit of malformed directories
- Clean up existing invalid directories (~/, PR #610..., git diff..., https:)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Implement path validation in CLAUDE.md generation to prevent invalid directory creation

- Added `isValidPathForClaudeMd()` function to validate file paths in `src/utils/claude-md-utils.ts`.
- Integrated path validation in `updateFolderClaudeMdFiles` to skip invalid paths.
- Added 6 new unit tests in `tests/utils/claude-md-utils.test.ts` to cover various rejection cases.
- Updated `.gitignore` to prevent tracking of invalid directories.
- Cleaned up existing invalid directories in the repository.

* feat: Promote critical WARN logs to ERROR level across codebase

Comprehensive log-level audit promoting 38+ WARN messages to ERROR for
improved debugging and incident response:

- Parser: observation type errors, data contamination
- SDK/Agents: empty init responses (Gemini, OpenRouter)
- Worker/Queue: session recovery, auto-recovery failures
- Chroma: sync failures, search failures (now treated as critical)
- SQLite: search failures (primary data store)
- Session/Generator: failures, missing context
- Infrastructure: shutdown, process management failures
- File Operations: CLAUDE.md updates, config reads
- Branch Management: recovery checkout failures

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Address PR #614 review issues

- Remove incorrectly tracked tilde-prefixed files from git
- Fix absolute path validation to check projectRoot boundaries
- Add test coverage for absolute path validation edge cases

Closes review issues:
- Issue 1: ~/ prefixed files removed from tracking
- Issue 3: Absolute paths now validated against projectRoot
- Issue 4: Added 3 new test cases for absolute path scenarios

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* build assets and context

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 23:34:20 -05:00

12 KiB

Issue #590: Blank Terminal Window Pops Up on Windows When Chroma MCP Server Starts

Date: 2026-01-07 Issue Author: dwd898 Severity: Medium (UX disruption, not a functional failure) Status: OPEN - Root cause confirmed, multiple solutions proposed


1. Executive Summary

On Windows 11, when claude-mem starts the Chroma MCP server via uvx, a blank terminal window (Windows Terminal / PowerShell) appears and does not close automatically. Users must manually close this window each time, which disrupts the workflow.

The root cause is that the MCP SDK's StdioClientTransport class does not pass the windowsHide: true option to the underlying child_process.spawn() call. While the claude-mem codebase attempts to set this option, it has no effect because the MCP SDK ignores it.

This issue affects all Windows users who have ChromaDB vector search enabled (the default configuration).


2. Problem Analysis

2.1 User-Reported Symptoms

  • A blank terminal window appears when any action triggers Chroma initialization
  • The window shows the uvx.exe path but contains no output
  • The window remains open until manually closed by the user
  • This occurs every time ChromaDB is initialized (typically once per Claude session)

2.2 Environment Details

Component Value
OS Windows 11 64-bit
Terminal PowerShell 7.6.0-preview.6
claude-mem version 9.0.0
uvx location C:\Users\Dell\AppData\Local\Microsoft\WinGet\Links\uvx.exe
MCP SDK version ^1.25.1

2.3 Trigger Conditions

The terminal popup occurs when:

  1. Claude Code starts a new session with claude-mem enabled
  2. A search query is executed with semantic search enabled
  3. The ChromaSync service initializes for the first time in a session
  4. Any backfill operation triggers Chroma connection

3. Technical Details

3.1 Affected Code Location

File: /Users/alexnewman/conductor/workspaces/claude-mem/budapest/src/services/sync/ChromaSync.ts

Lines: 106-124

const transportOptions: any = {
  command: 'uvx',
  args: [
    '--python', pythonVersion,
    'chroma-mcp',
    '--client-type', 'persistent',
    '--data-dir', this.VECTOR_DB_DIR
  ],
  stderr: 'ignore'
};

// CRITICAL: On Windows, try to hide console window to prevent PowerShell popups
// Note: windowsHide may not be supported by MCP SDK's StdioClientTransport
if (isWindows) {
  transportOptions.windowsHide = true;
  logger.debug('CHROMA_SYNC', 'Windows detected, attempting to hide console window', { project: this.project });
}

this.transport = new StdioClientTransport(transportOptions);

3.2 Why windowsHide Fails

The StdioClientTransport class from @modelcontextprotocol/sdk accepts configuration options but does not forward windowsHide to child_process.spawn(). The SDK's transport implementation only uses a subset of spawn options:

  • command - The executable to run
  • args - Command line arguments
  • env - Environment variables (optional)
  • stderr - Stderr handling mode

The windowsHide option is silently ignored because it's not part of the SDK's expected interface.

3.3 MCP SDK Transport Architecture

ChromaSync.ts
    |
    v
StdioClientTransport (MCP SDK)
    |
    v
child_process.spawn() [internal to SDK]
    |
    v
uvx.exe subprocess
    |
    v
chroma-mcp Python process

The SDK controls the spawn call, so claude-mem cannot directly influence the spawn options.

3.4 Comparison with Other Subprocess Calls

Other parts of claude-mem successfully hide Windows console windows because they use child_process.spawn() directly:

Component File Uses windowsHide Works on Windows
ProcessManager ProcessManager.ts:271 Yes (direct spawn) Yes
SDKAgent SDKAgent.ts:379 Yes (direct spawn) Yes
BranchManager BranchManager.ts:61,88 Yes (direct spawn) Yes
shared/paths paths.ts:103 Yes (direct spawn) Yes
ChromaSync ChromaSync.ts:120 Yes (via SDK - ignored) No

4. Impact Assessment

4.1 Affected Users

  • All Windows users with ChromaDB enabled (default)
  • Approximately 100% of Windows user base

4.2 Severity Breakdown

Aspect Impact
Functionality No impact - Chroma works correctly
UX Disruption Medium - Requires manual window close
Workflow Impact Low - One-time per session
Data Integrity None
Security None

4.3 Workaround Availability

Current Workaround: Users can manually close the terminal window. The Chroma process continues running in the background even after the window is closed.


5. Root Cause Analysis

5.1 Primary Cause

The MCP SDK's StdioClientTransport class does not implement support for the windowsHide spawn option. This is a limitation in the SDK, not a bug in claude-mem.

5.2 SDK Gap Analysis

The MCP SDK (version 1.25.1) provides a transport abstraction layer but does not expose all Node.js spawn options. The StdioClientTransport constructor signature accepts:

interface StdioClientTransportOptions {
  command: string;
  args?: string[];
  env?: NodeJS.ProcessEnv;
  stderr?: 'inherit' | 'pipe' | 'ignore';
}

Notable missing options:

  • windowsHide
  • detached
  • cwd
  • shell

5.3 Historical Context

The claude-mem codebase has extensively addressed Windows console popup issues in other areas:

  • December 4, 2025: Added windowsHide parameter to ProcessManager
  • December 17, 2025: PR #378 standardized windowsHide: true across all direct spawn calls
  • Known Issue: The comment in ChromaSync.ts (line 118) explicitly acknowledges this limitation

Approach: Wrap the uvx command in a PowerShell invocation that hides the window.

Implementation:

const transportOptions: any = {
  command: 'powershell',
  args: [
    '-WindowStyle', 'Hidden',
    '-Command',
    `uvx --python ${pythonVersion} chroma-mcp --client-type persistent --data-dir '${this.VECTOR_DB_DIR}'`
  ],
  stderr: 'ignore'
};

Pros:

  • No SDK changes required
  • Immediate fix possible
  • Pattern already used in worker-cli.js (lines 1-19)

Cons:

  • Adds PowerShell dependency (already required for Windows)
  • Slightly more complex command construction
  • PATH escaping considerations

Estimated Effort: 2-4 hours

6.2 Solution 2: Custom Transport Layer

Approach: Bypass StdioClientTransport and implement a custom transport using child_process.spawn() directly.

Implementation:

import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import { spawn, ChildProcess } from 'child_process';

class WindowsHiddenStdioTransport implements Transport {
  private process: ChildProcess;

  constructor(options: TransportOptions) {
    this.process = spawn(options.command, options.args, {
      windowsHide: true,
      stdio: ['pipe', 'pipe', options.stderr === 'ignore' ? 'ignore' : 'pipe']
    });
  }
  // ... implement Transport interface
}

Pros:

  • Full control over spawn options
  • Clean, maintainable solution
  • Reusable for other MCP clients

Cons:

  • Requires implementing Transport interface
  • Must handle stdin/stdout piping manually
  • More complex error handling

Estimated Effort: 8-16 hours

6.3 Solution 3: Upstream SDK Enhancement

Approach: Request the MCP SDK team to add windowsHide support to StdioClientTransport.

Implementation:

  1. Open issue on MCP SDK repository
  2. Propose API extension: spawnOptions?: Partial<SpawnOptions>
  3. Provide PR if accepted

Pros:

  • Fixes the root cause
  • Benefits all MCP SDK users on Windows
  • No workarounds needed

Cons:

  • Depends on external team
  • Uncertain timeline
  • May require SDK version bump

Estimated Effort: Variable (depends on upstream response)

6.4 Solution 4: VBS Wrapper Script

Approach: Use a Windows Script Host (VBS) file to launch the process silently.

Implementation:

Create launch-chroma.vbs:

Set WshShell = CreateObject("WScript.Shell")
WshShell.Run "uvx --python 3.13 chroma-mcp --client-type persistent --data-dir " & DataDir, 0, False

Pros:

  • Guaranteed hidden window
  • Works on all Windows versions

Cons:

  • Requires additional script file
  • Complex path handling
  • VBS is deprecated technology

Estimated Effort: 4-6 hours


7. Priority/Severity Assessment

7.1 Severity Matrix

Factor Rating Justification
User Impact Medium Annoying but not blocking
Frequency Low Once per session
Workaround Yes Close window manually
Data Risk None No data loss or corruption
Security Risk None No security implications

Priority: P2 (Medium)

This issue should be addressed in the next minor release but is not urgent enough to warrant an immediate patch release.

7.3 Recommendation

Implement Solution 1 (PowerShell Wrapper) as an immediate fix for the next release. Simultaneously, open an upstream issue for Solution 3 to address the root cause in the MCP SDK.


Issue Title Relationship
#367 Console windows appearing during hook execution Similar root cause
#517 PowerShell $_ escaping in Git Bash Windows shell escaping
#555 Windows hooks IPC issues Windows platform challenges
PR Title Relevance
#378 Windows stabilization Added windowsHide to other spawn calls
#372 Worker wrapper architecture Similar Windows console hiding approach

8.3 Documentation


9. Testing Recommendations

9.1 Test Cases

  1. Basic functionality: Verify Chroma starts correctly with proposed fix
  2. Window visibility: Confirm no terminal window appears
  3. Process lifecycle: Ensure Chroma process terminates on worker shutdown
  4. Error handling: Verify errors are properly captured despite hidden window
  5. PATH variations: Test with uvx in different PATH locations

9.2 Test Environments

  • Windows 11 with PowerShell 7.x
  • Windows 11 with PowerShell 5.1
  • Windows 10 with PowerShell 5.1
  • Windows with Git Bash as default shell

10. Appendix

10.1 Current ChromaSync Connection Flow

1. ChromaSync.ensureConnection() called
2. Check if already connected
3. Load Python version from settings
4. Detect Windows platform
5. Set windowsHide: true (ineffective)
6. Create StdioClientTransport with uvx command
7. Connect MCP client to transport
   -> POPUP APPEARS HERE
8. Mark as connected

10.2 PowerShell Command Pattern (from worker-cli.js)

The existing pattern for hidden PowerShell execution:

const cmd = `Start-Process -FilePath '${escapedPath}' -ArgumentList '${args}' -WindowStyle Hidden`;
spawnSync("powershell", ["-Command", cmd], {
  stdio: "pipe",
  timeout: 10000,
  windowsHide: true
});

10.3 MCP SDK Source Reference

The StdioClientTransport implementation in @modelcontextprotocol/sdk uses:

this._process = spawn(command, args, {
  env: this._env,
  stdio: ['pipe', 'pipe', stderr]
});

Note the absence of windowsHide in the spawn options.


11. Revision History

Date Author Changes
2026-01-07 Claude Opus 4.5 Initial report