Summary
Importing @anthropic-ai/claude-agent-sdk can cause a Node.js/TypeScript service to hang indefinitely during module initialization when the service is run with tsx watch under concurrently on Windows.
The same service starts successfully when run directly with tsx watch, and also starts successfully when using concurrently --raw.
This appears to be an interaction between:
- Windows process/stdio handling
tsx watch
concurrently's piped/prefixed stdio
@anthropic-ai/claude-agent-sdk
Environment
- OS: Windows
- Runtime: Node.js
- TypeScript runner:
tsx
- Process runner:
concurrently
- Claude dependency:
@anthropic-ai/claude-agent-sdk
Expected behavior
The service should finish importing its dependencies and reach:
regardless of whether it is launched directly or through concurrently.
Actual behavior
When launched through the normal concurrently configuration, the ingest process remains alive but hangs during module loading.
It never reaches app.listen(4001).
As a result, nothing is listening on port 4001, and another service attempting to call the ingest API receives a connection failure.
The process does not appear to crash or throw an exception. It remains alive with negligible CPU activity.
Process setup
The development command runs three services:
concurrently
├── web → next dev
├── ingest → tsx watch
└── worker → tsx
Only the ingest service uses tsx watch.
The relevant dependency chain was:
ingest
↓
@relay/agents
↓
providers/claude.ts
↓
@anthropic-ai/claude-agent-sdk
Reproduction / isolation
The issue was isolated by testing the imports individually.
The hang occurs when importing:
@anthropic-ai/claude-agent-sdk
through the Claude provider.
The sibling Gemini provider using:
does not exhibit the same behavior.
Test 1: Run ingest directly
tsx watch packages/ingest/src/server.ts
Result:
The server successfully reaches app.listen(4001).
Test 2: Run through concurrently
with the normal piped/prefixed output.
Result:
Hangs during module initialization
The process remains alive but never reaches app.listen(4001).
Test 3: Run through concurrently with --raw
Result:
This suggests that concurrently's normal stdio handling/prefixing is involved in the interaction.
Why this is significant
The Claude provider was statically imported by the provider registry:
import { ClaudeProvider } from "./providers/claude";
Although the registry constructs providers lazily, the static import causes the Claude provider and its dependency tree to be loaded during application startup.
In this application, ingest only uses the analyst and clarifier roles, both of which default to Gemini. A ClaudeProvider is therefore not constructed during ingest startup.
The Claude Agent SDK was nevertheless being loaded on every boot.
Workaround
The Claude provider was changed from an eager static dependency to a dynamic import inside the execution path:
const { ClaudeProvider } = await import("./providers/claude");
This removes the Claude Agent SDK from the ingest/web startup path.
The resulting behavior is:
Application startup
↓
Registry loads
↓
Gemini provider available
↓
app.listen(4001)
↓
Server ready
Claude requested later
↓
dynamic import()
↓
Claude Agent SDK loaded
↓
ClaudeProvider executes
This preserves:
concurrently
- prefixed
[web], [ingest], [worker] logs
tsx watch
- ingest hot reload
without loading the Claude SDK during startup.
Questions
- Is
@anthropic-ai/claude-agent-sdk expected to perform any process/stdio initialization during module import?
- Could the SDK's initialization interact with piped stdout/stderr or non-TTY streams?
- Is this combination of Windows +
tsx watch + concurrently known to cause an import hang?
- If the SDK requires specific stdin/stdout behavior, could the import avoid performing that initialization until the SDK is actually instantiated/used?
- Is there any recommended configuration for running the SDK under
concurrently and tsx watch on Windows?
Summary
Importing
@anthropic-ai/claude-agent-sdkcan cause a Node.js/TypeScript service to hang indefinitely during module initialization when the service is run withtsx watchunderconcurrentlyon Windows.The same service starts successfully when run directly with
tsx watch, and also starts successfully when usingconcurrently --raw.This appears to be an interaction between:
tsx watchconcurrently's piped/prefixed stdio@anthropic-ai/claude-agent-sdkEnvironment
tsxconcurrently@anthropic-ai/claude-agent-sdkExpected behavior
The service should finish importing its dependencies and reach:
regardless of whether it is launched directly or through
concurrently.Actual behavior
When launched through the normal
concurrentlyconfiguration, the ingest process remains alive but hangs during module loading.It never reaches
app.listen(4001).As a result, nothing is listening on port
4001, and another service attempting to call the ingest API receives a connection failure.The process does not appear to crash or throw an exception. It remains alive with negligible CPU activity.
Process setup
The development command runs three services:
Only the ingest service uses
tsx watch.The relevant dependency chain was:
Reproduction / isolation
The issue was isolated by testing the imports individually.
The hang occurs when importing:
through the Claude provider.
The sibling Gemini provider using:
does not exhibit the same behavior.
Test 1: Run ingest directly
Result:
The server successfully reaches
app.listen(4001).Test 2: Run through concurrently
with the normal piped/prefixed output.
Result:
The process remains alive but never reaches
app.listen(4001).Test 3: Run through concurrently with
--rawResult:
This suggests that
concurrently's normal stdio handling/prefixing is involved in the interaction.Why this is significant
The Claude provider was statically imported by the provider registry:
Although the registry constructs providers lazily, the static import causes the Claude provider and its dependency tree to be loaded during application startup.
In this application, ingest only uses the analyst and clarifier roles, both of which default to Gemini. A
ClaudeProvideris therefore not constructed during ingest startup.The Claude Agent SDK was nevertheless being loaded on every boot.
Workaround
The Claude provider was changed from an eager static dependency to a dynamic import inside the execution path:
This removes the Claude Agent SDK from the ingest/web startup path.
The resulting behavior is:
This preserves:
concurrently[web],[ingest],[worker]logstsx watchwithout loading the Claude SDK during startup.
Questions
@anthropic-ai/claude-agent-sdkexpected to perform any process/stdio initialization during module import?tsx watch+concurrentlyknown to cause an import hang?concurrentlyandtsx watchon Windows?