[BUG] Claude Code hangs when spawned from Node.js test environments
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:
- Create a Node.js test that spawns Claude Code as a child process
- Use any simple command like: claude --print "hello world" --model sonnet --debug
- 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);
});
});This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗