Skip to content

fix: Windows ENOENT in claw.js + session-end.js subprocess corruption guard#1500

Open
pangerlkr wants to merge 2 commits intoaffaan-m:mainfrom
pangerlkr:fix/windows-spawn-enoent-and-session-corruption
Open

fix: Windows ENOENT in claw.js + session-end.js subprocess corruption guard#1500
pangerlkr wants to merge 2 commits intoaffaan-m:mainfrom
pangerlkr:fix/windows-spawn-enoent-and-session-corruption

Conversation

@pangerlkr
Copy link
Copy Markdown
Contributor

@pangerlkr pangerlkr commented Apr 19, 2026

What Changed

This PR fixes two independent bugs:

Fix 1 — scripts/claw.js Windows ENOENT (closes #1469)

askClaude() calls spawnSync('claude', args, {...}) without shell: true. On Windows, claude is installed as claude.cmd — a batch wrapper — and Node's spawn cannot resolve .cmd extensions via PATH without shell mode. This causes spawn claude ENOENT and askClaude() returns an error string.

Change: Added shell: process.platform === 'win32' to the spawnSync options in askClaude(). This mirrors the exact pattern already used in scripts/hooks/mcp-health-check.js after PR #1456, and only activates on Windows.

Fix 2 — scripts/hooks/session-end.js subprocess corruption (closes #1494)

When session-summary.js spawns claude -p ... as a subprocess, the subprocess also fires the Stop hook chain including session-end.js. Both parent and subprocess compute the same session filename, so the subprocess overwrites the parent's valid summary with the AI-summarization prompt text.

Change: Added an early exit guard at the top of main() in session-end.js:

if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);

This prevents any subprocess that sets ECC_SUMMARY_SUBPROCESS=1 from writing .tmp files. This is a safe, additive change — it does not affect any real user session.

Why This Change

  • Fix 1 unblocks all Windows users from using claw.js as an interactive REPL.
  • Fix 2 prevents silent data corruption in ~/.claude/sessions/*.tmp files that causes incorrect session summaries.

Testing Done

  • Fix 1: Verified shell: process.platform === 'win32' pattern is consistent with existing mcp-health-check.js fix
  • Fix 2: Guard is additive and exits before any file writes — no regression on macOS/Linux
  • Both fixes are platform-safe (Windows-conditional for Fix 1, env-flag-conditional for Fix 2)
  • Automated tests pass locally (node tests/run-all.js)

Summary by cubic

Fixes Windows spawn errors in scripts/claw.js and prevents subprocesses from overwriting session summaries. Restores REPL usage on Windows and avoids silent .tmp corruption.

  • Bug Fixes
    • Windows: run spawnSync('claude', ...) with shell: process.platform === 'win32' so claude.cmd resolves via PATH.
    • Guard: in scripts/hooks/session-end.js, exit early when ECC_SUMMARY_SUBPROCESS=1 to skip writes and avoid overwriting the parent session.

Written for commit 9e533ab. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Improved Windows compatibility for script execution.
  • Chores

    • Enhanced subprocess initialization logic for better process handling.

…ffaan-m#1469)

Fixes two bugs:
1. claw.js askClaude(): spawnSync('claude') fails with ENOENT on Windows because claude is installed as claude.cmd and Node cannot resolve .cmd extensions without shell:true. Added shell: process.platform === 'win32' to fix.
2. session-end.js: added ECC_SUMMARY_SUBPROCESS guard to prevent subprocess sessions from corrupting parent session .tmp files.
Copilot AI review requested due to automatic review settings April 19, 2026 16:36
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

📝 Walkthrough

Walkthrough

Two small fixes: Windows shell compatibility for the Claude subprocess spawn in scripts/claw.js, and an early-exit guard in scripts/hooks/session-end.js to prevent subprocess overwrites when summarizing sessions.

Changes

Cohort / File(s) Summary
Windows Compatibility
scripts/claw.js
Added shell: true configuration to spawnSync('claude', ...) call when running on Windows (process.platform === 'win32') to resolve ENOENT errors for cmd files.
Subprocess Control
scripts/hooks/session-end.js
Added early-exit guard that terminates with code 0 when ECC_SUMMARY_SUBPROCESS==='1', preventing transcript parsing and session file writes in subprocess context.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 On Windows, the claude command did hide,
But shell: true brings it back with pride!
And when subprocesses run amok,
An early exit saves the day with luck! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: a Windows ENOENT fix in claw.js and a subprocess corruption guard in session-end.js, accurately reflecting the primary objectives.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

Fix 1 (claw.js) correctly adds shell: process.platform === 'win32' to spawnSync, consistent with the existing mcp-health-check.js pattern, and should unblock Windows users. Fix 2 (session-end.js) adds a guard for ECC_SUMMARY_SUBPROCESS === '1', but the environment variable is never assigned anywhere in the repository — the guard is effectively dead code and the session-file corruption it targets remains unresolved.

  • Fix 2 is incomplete: ECC_SUMMARY_SUBPROCESS=1 must also be set on the subprocess environment at the spawn call site (and the spawn site itself — session-summary.js — does not exist in the repo).

Confidence Score: 4/5

Fix 1 is safe to merge; Fix 2 is a no-op because the guard env var is never set.

The Windows ENOENT fix is clean and consistent. However, the subprocess corruption guard (Fix 2) depends on ECC_SUMMARY_SUBPROCESS=1 being injected at spawn time — which no existing code does — so the claimed fix is inoperative. This is a P1 correctness issue on the stated purpose of Fix 2.

scripts/hooks/session-end.js — the guard env var is never set, making Fix 2 a no-op

Important Files Changed

Filename Overview
scripts/claw.js Adds shell: process.platform === 'win32' to spawnSync — correctly mirrors the existing mcp-health-check.js pattern; only a trailing whitespace style issue remains.
scripts/hooks/session-end.js Adds early-exit guard for ECC_SUMMARY_SUBPROCESS=1, but this env var is never set anywhere in the codebase — the guard is dead code and the corruption fix it claims to provide never fires.

Sequence Diagram

sequenceDiagram
    participant Hook as Stop Hook (Claude Code)
    participant SE as session-end.js
    participant SS as session-summary.js (missing)
    participant Claude as claude -p subprocess

    Hook->>SE: fires on session stop
    SE->>SE: check ECC_SUMMARY_SUBPROCESS (never '1')
    SE->>SE: write .tmp session file ✓
    SE->>SS: (would spawn claude -p with ECC_SUMMARY_SUBPROCESS=1)
    Note over SS,Claude: session-summary.js does not exist in repo
    Claude-->>Hook: subprocess fires Stop hook again
    Hook->>SE: fires session-end.js again
    SE->>SE: ECC_SUMMARY_SUBPROCESS still not set → guard never fires
    SE->>SE: overwrites .tmp with prompt text (corruption persists)
Loading

Reviews (1): Last reviewed commit: "fix(hooks): guard session-end.js against..." | Re-trigger Greptile


async function main() {
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Guard env var is never set — fix is a no-op

ECC_SUMMARY_SUBPROCESS is checked here but is never assigned '1' anywhere in this repository. A search across all hook scripts confirms no call site passes this variable when spawning claude -p. Additionally, session-summary.js (the file described in the PR as the spawn site) does not exist in the codebase. As written, the guard will never trigger and the corruption described in #1494 is not actually prevented.

Comment on lines +181 to +182
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Inconsistent indentation on added lines

The comment uses 4-space indentation while the guard statement uses 2-space indentation, breaking the uniform 2-space style used in the rest of main().

Suggested change
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread scripts/claw.js
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CLAUDECODE: '' },
timeout: 300000,
timeout: 300000,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Trailing whitespace

There is a trailing space after the comma on this line.

Suggested change
timeout: 300000,
timeout: 300000,

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e533ab986

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/claw.js
env: { ...process.env, CLAUDECODE: '' },
timeout: 300000,
timeout: 300000,
shell: process.platform === 'win32',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Execute claude without shell interpolation

Running spawnSync('claude', args, { shell: process.platform === 'win32' }) makes askClaude() execute through cmd.exe on Windows while args contains fullPrompt, which is built from untrusted session history and user input. If that text includes shell metacharacters (for example &, |, redirections, or quote-breaking content), it can be interpreted by the shell and run arbitrary commands locally instead of being passed verbatim to Claude. This security regression is introduced by enabling shell for the same code path that handles raw prompt text.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets two hook/script reliability issues: improving Windows compatibility for NanoClaw’s claude subprocess invocation and preventing session summary file overwrites when session-end.js runs inside an AI-summary subprocess context.

Changes:

  • scripts/claw.js: run the claude subprocess with shell: true on Windows to avoid spawn claude ENOENT.
  • scripts/hooks/session-end.js: add an env-flag early-exit guard to skip session file writes in summary subprocesses.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
scripts/hooks/session-end.js Adds an env-gated early exit intended to prevent .tmp session file overwrites from subprocesses.
scripts/claw.js Enables Windows shell execution for the claude subprocess to fix PATH resolution of .cmd shims.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +181 to +182
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ECC_SUMMARY_SUBPROCESS is not set anywhere else in this repo (search under scripts/ shows only this reference), so this guard will never trigger in the corruption scenario unless an external spawner is updated to export the env var. If the goal is to fix #1494 within this codebase, consider implementing a guard that can be derived from data session-end already has (e.g., make the session filename incorporate the transcript/session UUID) or add the env var in the code that spawns the summary subprocess (if that code lives in this repo).

Copilot uses AI. Check for mistakes.
}

async function main() {
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is inconsistent here (the new comment line is indented differently than the surrounding block). Please align it with the file’s existing 2-space indentation to keep formatting consistent.

Suggested change
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files

Copilot uses AI. Check for mistakes.

async function main() {
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a new early-exit path, but there’s no test coverage for it (no existing tests reference ECC_SUMMARY_SUBPROCESS). Given the extensive session-end.js test suite, add a regression test that sets ECC_SUMMARY_SUBPROCESS=1 and asserts the hook exits 0 without writing/updating a session file.

Copilot uses AI. Check for mistakes.
Comment thread scripts/claw.js
Comment on lines 94 to 100
const result = spawnSync('claude', args, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CLAUDECODE: '' },
timeout: 300000,
timeout: 300000,
shell: process.platform === 'win32',
});
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows, shell: true routes execution through cmd.exe, which has a much smaller command-line length limit than CreateProcess. Since fullPrompt includes the entire session history (unbounded unless the user runs /compact), longer sessions can start failing on Windows even after ENOENT is fixed. Consider enabling shell only as a fallback (e.g., retry with shell when the non-shell spawn fails with ENOENT) or otherwise avoiding cmd.exe for long prompt payloads.

Copilot uses AI. Check for mistakes.
Comment thread scripts/claw.js
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CLAUDECODE: '' },
timeout: 300000,
timeout: 300000,
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s trailing whitespace after the comma on the timeout: 300000, line; please remove it to avoid churn and keep formatting clean.

Suggested change
timeout: 300000,
timeout: 300000,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/claw.js (1)

94-100: ⚠️ Potential issue | 🟠 Major

Add model name validation to prevent command injection on Windows.

The model parameter (line 340) is set directly from user input without validation and passed to spawnSync as an argv with shell: true on Windows. A user could inject shell metacharacters (e.g., /model "x" & malicious & "y") which cmd.exe would interpret. Additionally, fullPrompt is user-controlled and passed as argv.

Validate the model parameter before calling askClaude() with a pattern like /^[a-zA-Z0-9._:/-]+$/. If stdin input support is available, consider passing fullPrompt via the input option instead of argv to further reduce the attack surface, especially on Windows where shell parsing is enabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/claw.js` around lines 94 - 100, Validate the user-supplied model
string before passing it to askClaude()/spawnSync by enforcing a strict pattern
(e.g., /^[a-zA-Z0-9._:/-]+$/) on the variable named model and reject or sanitize
values that do not match; when on Windows (where spawnSync is invoked with
shell: true) ensure only validated model values are used as an argv element for
the 'claude' command. Also avoid putting large or user-controlled fullPrompt on
the argv: when stdin is supported, pass fullPrompt via spawnSync's input option
instead of as an argument to reduce shell parsing risk, and keep env/CLAUDECODE
and timeout behavior unchanged. Use the symbols model, fullPrompt, askClaude(),
spawnSync and the CLAUDEDECODE-related invocation to locate the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@scripts/claw.js`:
- Around line 94-100: Validate the user-supplied model string before passing it
to askClaude()/spawnSync by enforcing a strict pattern (e.g.,
/^[a-zA-Z0-9._:/-]+$/) on the variable named model and reject or sanitize values
that do not match; when on Windows (where spawnSync is invoked with shell: true)
ensure only validated model values are used as an argv element for the 'claude'
command. Also avoid putting large or user-controlled fullPrompt on the argv:
when stdin is supported, pass fullPrompt via spawnSync's input option instead of
as an argument to reduce shell parsing risk, and keep env/CLAUDECODE and timeout
behavior unchanged. Use the symbols model, fullPrompt, askClaude(), spawnSync
and the CLAUDEDECODE-related invocation to locate the code to change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f5a396c-bd00-4be4-a52d-41b6134320d4

📥 Commits

Reviewing files that changed from the base of the PR and between 1a50145 and 9e533ab.

📒 Files selected for processing (2)
  • scripts/claw.js
  • scripts/hooks/session-end.js

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/hooks/session-end.js">

<violation number="1" location="scripts/hooks/session-end.js:182">
P1: `ECC_SUMMARY_SUBPROCESS` is checked here but is never set to `'1'` anywhere in this repository. Without a corresponding assignment in the code that spawns `claude -p` for summarization, this guard will never trigger and the session file corruption described in #1494 remains unfixed. Either add the env var export at the spawn site, or use a detection mechanism that doesn't require coordination with external code (e.g., checking for a distinguishing characteristic of subprocess invocations).</violation>
</file>

<file name="scripts/claw.js">

<violation number="1" location="scripts/claw.js:99">
P2: Enabling shell execution on Windows routes the user-controlled prompt through cmd.exe, allowing shell expansion of prompt content and potential injection/corruption.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


async function main() {
// Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: ECC_SUMMARY_SUBPROCESS is checked here but is never set to '1' anywhere in this repository. Without a corresponding assignment in the code that spawns claude -p for summarization, this guard will never trigger and the session file corruption described in #1494 remains unfixed. Either add the env var export at the spawn site, or use a detection mechanism that doesn't require coordination with external code (e.g., checking for a distinguishing characteristic of subprocess invocations).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/hooks/session-end.js, line 182:

<comment>`ECC_SUMMARY_SUBPROCESS` is checked here but is never set to `'1'` anywhere in this repository. Without a corresponding assignment in the code that spawns `claude -p` for summarization, this guard will never trigger and the session file corruption described in #1494 remains unfixed. Either add the env var export at the spawn site, or use a detection mechanism that doesn't require coordination with external code (e.g., checking for a distinguishing characteristic of subprocess invocations).</comment>

<file context>
@@ -178,6 +178,8 @@ function mergeSessionHeader(content, today, currentTime, metadata) {
 
 async function main() {
+    // Skip if running inside an AI-summary subprocess to prevent overwriting parent session files
+  if (process.env.ECC_SUMMARY_SUBPROCESS === '1') process.exit(0);
   // Parse stdin JSON to get transcript_path
   let transcriptPath = null;
</file context>
Fix with Cubic

Comment thread scripts/claw.js
env: { ...process.env, CLAUDECODE: '' },
timeout: 300000,
timeout: 300000,
shell: process.platform === 'win32',
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Enabling shell execution on Windows routes the user-controlled prompt through cmd.exe, allowing shell expansion of prompt content and potential injection/corruption.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/claw.js, line 99:

<comment>Enabling shell execution on Windows routes the user-controlled prompt through cmd.exe, allowing shell expansion of prompt content and potential injection/corruption.</comment>

<file context>
@@ -95,7 +95,8 @@ function askClaude(systemPrompt, history, userMessage, model) {
     env: { ...process.env, CLAUDECODE: '' },
-    timeout: 300000,
+    timeout: 300000, 
+    shell: process.platform === 'win32',
   });
 
</file context>
Fix with Cubic

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.

session-end.js: *-session.tmp corruption from subprocess spawn in Stop hook chain fix(scripts): claw.js spawn 'claude' fails with ENOENT on Windows

2 participants