Skip to content

Comments

fix: improve Windows/WSL compatibility#36

Merged
subsy merged 1 commit intomainfrom
fix/windows-wsl-compatibility
Jan 14, 2026
Merged

fix: improve Windows/WSL compatibility#36
subsy merged 1 commit intomainfrom
fix/windows-wsl-compatibility

Conversation

@subsy
Copy link
Owner

@subsy subsy commented Jan 14, 2026

Summary

  • Fixes crash when loading corrupted/old session files missing trackerState
  • Fixes agent detection on native Windows (uses where.exe instead of which)
  • Consolidates command-finding logic into shared utility

Changes

Session File Validation (persistence.ts)

  • Added validateLoadedSession() to check required fields before casting
  • Invalid sessions now return null with helpful warning instead of crashing
  • Added defensive fallback in getSessionSummary() for extra safety

Cross-Platform Command Finding (base.ts)

  • New findCommandPath() utility that uses:
    • where on Windows
    • which on Unix/Linux/macOS
  • Handles Windows where returning multiple paths (takes first)

Agent Plugin Updates (claude.ts, opencode.ts)

  • Removed duplicate runWhich() implementations
  • Now use shared findCommandPath() utility

Test plan

  • Test on Windows native - agent detection should work
  • Test with corrupted session.json - should warn and allow fresh start
  • Test normal operation on Linux/macOS - no regressions

Known Issues

WSL stdin bug: Users on WSL may still encounter EPERM: operation not permitted, read even on Bun 1.3.3+. This is tracked upstream at oven-sh/bun#24615. The issue was reportedly fixed in 1.3.3 but may have regressed - user reports hitting it on 1.3.6.

Workarounds for WSL users:

  • Try bun upgrade --version 1.3.0 (pre-regression)
  • Run from native Windows PowerShell/CMD instead of WSL
  • Use Node.js instead of Bun if available

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved session persistence validation to better handle incomplete or corrupted session data, with enhanced error logging.
    • Enhanced reliability of command-line tool detection across different operating systems.
  • Refactor

    • Streamlined cross-platform command detection logic for improved maintainability and consistency.
    • Strengthened session loading with defensive checks against missing data fields.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add session file validation to prevent crash on corrupted/old sessions
- Add cross-platform command finder (uses 'where' on Windows, 'which' on Unix)
- Refactor agent detection to use shared findCommandPath utility
- Add defensive handling in getSessionSummary for missing trackerState

Fixes crash: TypeError: undefined is not an object (evaluating 'state.trackerState.totalTasks')
Fixes: Agent 'opencode' not available on Windows (which command not found)
@vercel
Copy link

vercel bot commented Jan 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
ralph-tui Ready Ready Preview, Comment Jan 14, 2026 2:19pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Walkthrough

This pull request consolidates command path detection logic across agent plugins into a shared utility function with platform-appropriate tooling, and introduces structured validation for persisted sessions with defensive null-safety fallbacks for missing state.

Changes

Cohort / File(s) Summary
Command path detection refactoring
src/plugins/agents/base.ts, src/plugins/agents/builtin/claude.ts, src/plugins/agents/builtin/opencode.ts
Extracted platform-specific command lookup into new findCommandPath() utility that uses which (Unix) or where (Windows) with 5-second timeout. Removed duplicate runWhich() helpers from claude and opencode plugins; both now call the centralised utility instead. Updated path resolution and version verification logic accordingly.
Session persistence validation
src/session/persistence.ts
Added validateLoadedSession() private function to enforce structural requirements (sessionId, status, trackerState with plugin/totalTasks/tasks fields). Modified loadPersistedSession() to parse then validate, logging warnings on failure. Enhanced getSessionSummary() with defensive fallbacks for missing trackerState fields (unknown plugin, zero tasks).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit's tale of paths now clear,
One search for commands far and near—
Windows, Unix, all found with care,
Sessions validated, robust and fair!
Code consolidated, no duplicates here,
Our warren grows stronger, year after year.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: improve Windows/WSL compatibility' directly reflects the main objectives of this PR, which are fixing Windows agent detection and improving session file handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
src/plugins/agents/base.ts (2)

23-70: Well-implemented cross-platform command detection utility.

The function correctly handles:

  • Platform detection and appropriate tool selection (where vs which)
  • Windows-specific shell: true requirement for PATH resolution
  • Multiple path handling from where output
  • Timeout with process termination

However, there's a potential race condition: if proc.kill() in the timeout handler fires just before the close event, both could attempt to resolve the Promise. While this won't cause a crash (the second resolve is a no-op), it may lead to inconsistent behaviour where the timeout path reports found: false but the actual process succeeded.

Consider tracking resolution state:

♻️ Suggested improvement
 export function findCommandPath(
   command: string
 ): Promise<{ found: boolean; path: string }> {
   return new Promise((resolve) => {
     const isWindows = platform() === 'win32';
     const whichCmd = isWindows ? 'where' : 'which';
+    let resolved = false;
+
+    const safeResolve = (result: { found: boolean; path: string }) => {
+      if (!resolved) {
+        resolved = true;
+        resolve(result);
+      }
+    };

     const proc = spawn(whichCmd, [command], {
       stdio: ['ignore', 'pipe', 'pipe'],
       shell: isWindows,
     });

     let stdout = '';

     proc.stdout?.on('data', (data: Buffer) => {
       stdout += data.toString();
     });

     proc.on('error', () => {
-      resolve({ found: false, path: '' });
+      safeResolve({ found: false, path: '' });
     });

     proc.on('close', (code) => {
       if (code === 0 && stdout.trim()) {
         const firstPath = stdout.trim().split(/\r?\n/)[0] ?? '';
-        resolve({ found: true, path: firstPath.trim() });
+        safeResolve({ found: true, path: firstPath.trim() });
       } else {
-        resolve({ found: false, path: '' });
+        safeResolve({ found: false, path: '' });
       }
     });

     setTimeout(() => {
       proc.kill();
-      resolve({ found: false, path: '' });
+      safeResolve({ found: false, path: '' });
     }, 5000);
   });
 }

72-72: Unconventional import placement.

The import statement at line 72 appears after the findCommandPath function definition. While syntactically valid due to hoisting, this is an unusual pattern that may confuse readers. Consider moving it to the top of the file with other imports.


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea6e55 and 996a957.

📒 Files selected for processing (4)
  • src/plugins/agents/base.ts
  • src/plugins/agents/builtin/claude.ts
  • src/plugins/agents/builtin/opencode.ts
  • src/session/persistence.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Start all code files with a file-level JSDoc comment section explaining the file's purpose, prefixed with 'ABOUTME: '

Files:

  • src/plugins/agents/builtin/claude.ts
  • src/plugins/agents/base.ts
  • src/plugins/agents/builtin/opencode.ts
  • src/session/persistence.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Start all code files with a file-level JSDoc comment section explaining the file's purpose, prefixed with "ABOUTME: "

Files:

  • src/plugins/agents/builtin/claude.ts
  • src/plugins/agents/base.ts
  • src/plugins/agents/builtin/opencode.ts
  • src/session/persistence.ts
🧬 Code graph analysis (3)
src/plugins/agents/builtin/claude.ts (2)
src/plugins/agents/types.ts (1)
  • AgentDetectResult (10-22)
src/plugins/agents/base.ts (1)
  • findCommandPath (30-70)
src/plugins/agents/builtin/opencode.ts (2)
src/plugins/agents/types.ts (1)
  • AgentDetectResult (10-22)
src/plugins/agents/base.ts (1)
  • findCommandPath (30-70)
src/session/persistence.ts (1)
src/session/index.ts (2)
  • PersistedSessionState (394-394)
  • isSessionResumable (386-386)
🔇 Additional comments (7)
src/plugins/agents/builtin/opencode.ts (2)

9-9: LGTM!

The import correctly references the new shared findCommandPath utility from the base module.


138-171: Clean migration to shared utility.

The detect() method now properly uses the cross-platform findCommandPath utility. The implementation:

  • Correctly checks findResult.found before proceeding
  • Passes findResult.path to version verification
  • Provides a helpful installation message when the CLI is not found
  • Consistently uses findResult.path for executablePath in all return paths
src/plugins/agents/builtin/claude.ts (2)

9-9: LGTM!

The import correctly references the shared findCommandPath utility.


117-150: Consistent migration to shared utility.

The detect() method follows the same pattern as opencode.ts, correctly using the cross-platform findCommandPath utility. The implementation is consistent and correct.

src/session/persistence.ts (3)

174-210: Well-structured session validation.

The validateLoadedSession function provides thorough validation of required fields before casting to PersistedSessionState. This correctly addresses the PR objective of fixing crashes when loading corrupted or old session files.

The validation covers:

  • Basic object structure
  • Required top-level fields (sessionId, status)
  • Required nested trackerState with its sub-fields (plugin, totalTasks, tasks)

The error messages are descriptive and help users understand what's wrong.


220-249: Graceful handling of invalid session files.

The updated loadPersistedSession correctly:

  • Validates the parsed JSON before use
  • Returns null with a warning instead of crashing on invalid data
  • Handles version defaulting for backward compatibility

This ensures users with corrupted or old session files get a helpful message rather than a crash.


645-666: Defensive fallback for trackerState in summary.

The fallback at lines 646-650 provides an additional safety net. While loadPersistedSession now validates trackerState, this defensive coding protects against:

  • Manually constructed PersistedSessionState objects that bypass loading
  • Future code paths that might not go through loadPersistedSession

The nullish coalescing operators on lines 660 and 664 are slightly redundant given the fallback object guarantees the properties exist, but they're harmless and add an extra layer of safety.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@subsy subsy merged commit b6cccc9 into main Jan 14, 2026
5 checks passed
@subsy subsy deleted the fix/windows-wsl-compatibility branch January 14, 2026 14:24
sakaman pushed a commit to sakaman/ralph-tui that referenced this pull request Feb 15, 2026
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