claude-git-hooks 2.6.0 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,49 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
5
5
  El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.6.2] - 2025-12-12
9
+
10
+ ### 🐛 Fixed
11
+
12
+ - **Parallel analysis stdin error handling** - Added graceful error handling for EOF errors during parallel execution (#43)
13
+ - **What was broken**: Unhandled 'error' events on stdin stream when Claude CLI process terminates unexpectedly during parallel analysis
14
+ - **Root cause**: `stdin.write()` failures emit asynchronous 'error' events that weren't being caught, causing uncaught exceptions with large prompts (18+ files) in parallel mode
15
+ - **Fix**: Added explicit error handler on `stdin` stream before writing prompt
16
+ - **Files changed**:
17
+ - `lib/utils/claude-client.js:287-308` - Added stdin error handler with detailed diagnostics
18
+ - **Impact**:
19
+ - Provides clear error message: "Failed to write to Claude stdin - process terminated unexpectedly"
20
+ - Logs diagnostic info: prompt length, error code, duration
21
+ - Suggests actionable fix: "Try reducing batch size or number of files per commit"
22
+ - **Compatibility**: No breaking changes, improves error reporting for all platforms
23
+
24
+ ### 🎯 User Experience
25
+
26
+ - **Before**: Cryptic unhandled 'write EOF' exceptions crashed commit process with no actionable information
27
+ - **After**: Clear error message with diagnostic context and suggested remediation
28
+ - **Debug**: Added structured logging with prompt length and timing when EOF errors occur
29
+
30
+ ## [2.6.1] - 2025-12-04
31
+
32
+ ### 🐛 Fixed
33
+
34
+ - **Windows spawn ENOENT errors** - Fixed critical issue where Claude CLI failed to execute on Windows with npm-installed tools
35
+ - **What was broken**: `spawn()` cannot execute `.cmd`/`.bat` files directly on Windows, causing `ENOENT` errors
36
+ - **Root cause**: npm on Windows creates both `claude` (non-executable) and `claude.cmd` (executable), and system was selecting the wrong one
37
+ - **Fix 1**: `which-command.js` now prefers `.cmd`/`.bat` extensions over extensionless entries when multiple matches found
38
+ - **Fix 2**: `claude-client.js` wraps `.cmd`/`.bat` commands with `cmd.exe /c` before spawning
39
+ - **Files changed**:
40
+ - `lib/utils/which-command.js:135-163` - Prefer `.cmd`/`.bat` in Windows path resolution
41
+ - `lib/utils/claude-client.js:165-179` - Wrap batch files with cmd.exe
42
+ - **Impact**: Fixes `analyze-diff` and commit hooks for Windows users with npm-installed Claude CLI
43
+ - **Compatibility**: No impact on Linux/macOS or Windows WSL setups, only affects Windows native npm installations
44
+
45
+ ### 🎯 User Experience
46
+
47
+ - **Before**: Windows users with npm-installed Claude CLI got `ENOENT` errors on every command
48
+ - **After**: Commands work seamlessly, same as other platforms
49
+ - **Debug**: Added detailed logging for Windows .cmd file detection and wrapping
50
+
8
51
  ## [2.6.0] - 2025-12-02
9
52
 
10
53
  ### ✨ Added - Node.js 24 Compatibility
@@ -162,7 +162,23 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
162
162
  finalArgs.push('--allowedTools', allowedTools.join(','));
163
163
  }
164
164
 
165
- const fullCommand = finalArgs.length > 0 ? `${command} ${finalArgs.join(' ')}` : command;
165
+ // CRITICAL FIX: Windows .cmd/.bat file handling
166
+ // Why: spawn() cannot execute .cmd/.bat files directly on Windows (ENOENT error)
167
+ // Solution: Wrap with cmd.exe /c when command ends with .cmd or .bat
168
+ // Impact: Only affects Windows npm-installed CLI tools, no impact on other platforms
169
+ let spawnCommand = command;
170
+ let spawnArgs = finalArgs;
171
+
172
+ if (isWindows() && (command.endsWith('.cmd') || command.endsWith('.bat'))) {
173
+ logger.debug('claude-client - executeClaude', 'Wrapping .cmd/.bat with cmd.exe', {
174
+ originalCommand: command,
175
+ originalArgs: finalArgs
176
+ });
177
+ spawnCommand = 'cmd.exe';
178
+ spawnArgs = ['/c', command, ...finalArgs];
179
+ }
180
+
181
+ const fullCommand = spawnArgs.length > 0 ? `${spawnCommand} ${spawnArgs.join(' ')}` : spawnCommand;
166
182
 
167
183
  logger.debug(
168
184
  'claude-client - executeClaude',
@@ -176,7 +192,8 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
176
192
  // spawn streams data, exec buffers everything in memory
177
193
  // Node 24 Fix: Removed shell: true to avoid DEP0190 deprecation warning
178
194
  // We now use absolute paths from which(), so shell is not needed
179
- const claude = spawn(command, finalArgs, {
195
+ // Windows .cmd/.bat fix: Wrapped with cmd.exe /c (see above)
196
+ const claude = spawn(spawnCommand, spawnArgs, {
180
197
  stdio: ['pipe', 'pipe', 'pipe'] // stdin, stdout, stderr
181
198
  });
182
199
 
@@ -263,13 +280,40 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
263
280
 
264
281
  // Write prompt to stdin
265
282
  // Why: Claude CLI reads prompt from stdin, not command arguments
283
+
284
+ // Handle stdin errors (e.g., EOF when process terminates unexpectedly)
285
+ // Why: write() failures can emit 'error' events asynchronously
286
+ // Common in parallel execution with large prompts
287
+ claude.stdin.on('error', (error) => {
288
+ logger.error(
289
+ 'claude-client - executeClaude',
290
+ 'stdin stream error (process may have terminated early)',
291
+ {
292
+ error: error.message,
293
+ code: error.code,
294
+ promptLength: prompt.length,
295
+ duration: Date.now() - startTime
296
+ }
297
+ );
298
+
299
+ reject(new ClaudeClientError('Failed to write to Claude stdin - process terminated unexpectedly', {
300
+ cause: error,
301
+ context: {
302
+ promptLength: prompt.length,
303
+ errorCode: error.code,
304
+ errorMessage: error.message,
305
+ suggestion: 'Try reducing batch size or number of files per commit'
306
+ }
307
+ }));
308
+ });
309
+
266
310
  try {
267
311
  claude.stdin.write(prompt);
268
312
  claude.stdin.end();
269
313
  } catch (error) {
270
314
  logger.error(
271
315
  'claude-client - executeClaude',
272
- 'Failed to write prompt to Claude CLI stdin',
316
+ 'Failed to write prompt to Claude CLI stdin (synchronous error)',
273
317
  error
274
318
  );
275
319
 
@@ -132,12 +132,32 @@ const whichViaCommand = (command) => {
132
132
  timeout: 3000 // Don't wait forever
133
133
  });
134
134
 
135
- // Windows 'where' returns multiple matches, take first
136
- const firstMatch = result.split('\n')[0].trim();
135
+ // Windows 'where' returns multiple matches
136
+ const matches = result.split('\n').map(line => line.trim()).filter(line => line.length > 0);
137
+
138
+ // CRITICAL FIX: On Windows, prefer .cmd/.bat over extensionless entries
139
+ // Why: npm creates both 'claude' and 'claude.cmd', but only .cmd is executable via spawn()
140
+ // Example: where claude returns:
141
+ // 1. C:\Users\...\npm\claude (NOT executable)
142
+ // 2. C:\Users\...\npm\claude.cmd (executable)
143
+ if (isWin && matches.length > 1) {
144
+ const cmdMatch = matches.find(m => m.endsWith('.cmd') || m.endsWith('.bat'));
145
+ if (cmdMatch) {
146
+ logger.debug('which-command - whichViaCommand', 'Preferring .cmd/.bat over extensionless', {
147
+ command,
148
+ preferred: cmdMatch,
149
+ allMatches: matches
150
+ });
151
+ return cmdMatch;
152
+ }
153
+ }
154
+
155
+ const firstMatch = matches[0];
137
156
 
138
157
  logger.debug('which-command - whichViaCommand', 'Found via command', {
139
158
  command,
140
- path: firstMatch
159
+ path: firstMatch,
160
+ totalMatches: matches.length
141
161
  });
142
162
 
143
163
  return firstMatch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-git-hooks",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
5
5
  "type": "module",
6
6
  "bin": {