[BUG] Claude Code hangs when spawned from Node.js test environments

Resolved 💬 3 comments Opened Aug 28, 2025 by ramarnat Closed Jan 5, 2026

Environment

  • Platform (select one):
  • [x] Anthropic API
  • [ ] AWS Bedrock
  • [ ] Google Vertex AI
  • [ ] Other: <!-- specify -->
  • Claude CLI version: 1.0.93
  • Operating System: 24.6.0
  • Terminal: ghostty
  • Node.js version: v23.11.1
  • Test framework: Vitest 3.2.4

Issue Description:
Claude Code hangs during initialization when spawned as a child process from Node.js test environments (vitest, likely jest as well). The process never completes and execution of the prompt and must be killed manually.

Reproduction Steps:

  1. Create a Node.js test that spawns Claude Code as a child process
  2. Use any simple command like: claude --print "hello world" --model sonnet --debug
  3. Run the test from vitest or similar test runner

Expected Behavior:
Claude Code should process the prompt and return results normally, as it does when run directly from terminal.

Actual Behavior:
Claude Code gets stuck. Debug output shows:

[DEBUG] Writing to temp file: /tmp/.claude.json.tmp.xxx
[DEBUG] File written atomically
[DEBUG] Applying permission update: Adding rules...
[DEBUG] Writing to temp file: /tmp/.claude.json.tmp.yyy
[DEBUG] File written atomically
[DEBUG] Applying permission update: Adding rules...

...hangs...

Tested Workarounds (all failed):

  • Isolated config directories with CLAUDE_CONFIG_PATH
  • Isolated HOME directories
  • CI environment variables (CI=true, NO_UPDATE_NOTIFIER=1)
  • --dangerously-skip-permissions flag
  • Different working directories (/tmp instead of project paths)
  • Sequential vs parallel test execution

Impact:

  • This prevents automated testing and CI/CD integration with Claude Code, limiting its use in validating prompt based programmatic workflows. For example, I am trying to confirm that a particular prompt produces a particular output and cannot do so.

Additional Context:
The issue appears specific to Node.js child_process spawning from test environments. Claude Code works perfectly when run directly from terminal or shell scripts.

When I looked closer it _seems_ like it hangs when it tries to do shell snapshot (hence some of the attempts to isolate home etc.)

I have reviewed the other reports of this, and they are all closed so this is NOT a duplicate of any open bug.

Sample Vitest

import { describe, test, expect, beforeAll } from 'vitest';
import { spawn } from 'child_process';

describe('Simple Claude Test', () => {
  let claudeResult: any; // Store Claude execution result for all tests

  beforeAll(async () => {
    console.log('Starting simple Claude test in beforeAll...');
    
    claudeResult = await new Promise<{ stdout: string; stderr: string; code: number }>((resolve, reject) => {
      const testConfigDir = '/tmp/claude-test-config-' + Math.random().toString(36).substring(7);
      const workingDir = '/tmp/random-' + Math.random().toString(36).substring(7);
      
      // Create directories
      require('fs').mkdirSync(testConfigDir, { recursive: true });
      require('fs').mkdirSync(workingDir, { recursive: true });
      
      const child = spawn('claude', ['--print', 'hello world test', '--model', 'sonnet', '--debug', '--dangerously-skip-permissions','--verbose', '--output-format', 'stream-json'], {
        cwd: workingDir,
        env: { 
          ...process.env, 
          CLAUDE_CONFIG_PATH: testConfigDir + '/claude.json',
          HOME: testConfigDir,  // Isolate Claude's home directory
          CI: 'true',  // Set CI environment
          NO_UPDATE_NOTIFIER: '1',  // Disable update checks
          CLAUDE_DISABLE_TELEMETRY: '1'  // Disable telemetry
        },
        stdio: ['pipe', 'pipe', 'pipe']
      });

      let stdout = '';
      let stderr = '';

      child.stdout?.on('data', (data) => {
        const output = data.toString();
        stdout += output;
        console.log('[CLAUDE-OUT]', output);
      });

      child.stderr?.on('data', (data) => {
        const output = data.toString();
        stderr += output;
        console.log('[CLAUDE-ERR]', output);
      });

      child.on('close', (code) => {
        resolve({ stdout, stderr, code: code || 0 });
      });

      child.on('error', (error) => {
        reject(error);
      });

      setTimeout(() => {
        console.log('TIMEOUT - Killing Claude process');
        console.log('Final stdout:', stdout.length, 'characters');
        console.log('Final stderr:', stderr.length, 'characters');
        console.log('Last stdout chunk:', stdout.slice(-500));
        child.kill('SIGTERM');
        reject(new Error('Claude test timed out'));
      }, 60000);
    });

    console.log('Claude execution completed in beforeAll');
    console.log('Claude exit code:', claudeResult.code);
    console.log('Claude stdout length:', claudeResult.stdout.length);
    console.log('Claude stderr length:', claudeResult.stderr.length);
  }, 120000);

  test('should run claude with hello world', async () => {
    expect(claudeResult.code).toBe(0);
  });
});

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗