[BUG] Claude Code TS SDK expects the prompt stream to remain open while working

Resolved 💬 6 comments Opened Oct 17, 2025 by gsabran Closed Jan 20, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

If the prompt is an AsyncIterable<SDKUserMessage> that is closed before Claude Code is done working, several features (hooks, canUseTool) are not called.

What Should Happen?

All features should work if the prompt's async iterable has completed before Claude Code is done.

Error Messages/Logs

Error in hook callback hook_0: Error: Stream closed

Steps to Reproduce

Claude Code TS SDK requires the following type for the prompt parameter: string | AsyncIterable<SDKUserMessage>

When sending messages other than a simple text, we have to use AsyncIterable<SDKUserMessage>. Several features, such as hooks, or canUseTool do not work when the AsyncIterable is not kept open while Claude Code is working (ref, ref). Other core features work. When adding a stderr handle, we see a log such as: Error in hook callback hook_0: Error: Stream closed

here's the query setup (unchanged between repros):

const queryHandle = query({
	prompt,
	options: {
		hooks: {
			PreToolUse: [
				{
					hooks: [
						async (input: HookInput): Promise<HookJSONOutput> => {
							console.log(`🔧 About to run tool: ${input.tool_name}`)
							return { continue: true }
						},
					],
				},
			],
		},
		stderr: (data: string) => {
			console.error(`Claude Code stderr: '${data}'`)
		},
	},
})

This works:

// Text message
const prompt = "Can you read the readme?"
const queryHandle = ...
for await (const event of queryHandle) {
	console.log(event)
}

This doesn't call the hook nor canUseTool, and stderr shows Error in hook callback hook_0: Error: Stream closed:

// Text message
const message: SDKUserMessage = { ... }
const prompt: AsyncIterable<SDKUserMessage> = {
	async *[Symbol.asyncIterator]() {
		yield message
	},
}
const queryHandle = ...
for await (const event of queryHandle) {
	console.log(event) // events are still logged, but the hook and canUseTool are not called as expected
}

keeping the prompt stream open fixes the issue:

let onReceiveResult: () => void = () => {}
const receivedResult = new Promise<void>((resolve) => {
	onReceiveResult = resolve
})


const message: SDKUserMessage = { ... }
const prompt: AsyncIterable<SDKUserMessage> = {
	async *[Symbol.asyncIterator]() {
		yield message
		await receivedResult
	},
}
const queryHandle = ...
for await (const event of queryHandle) {
	if (event.type == "result") {
		onReceiveResult()
	}
	console.log(event)
}

Claude Model

None

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

claude: 2.0.20 / @anthropic-ai/claude-agent-sdk@0.1.19

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

In addition to fixing this bug, I would recommend the TS SDK to not require an async parameter, ie to accept string | SDKUserMessage[] | AsyncIterable<SDKUserMessage>

View original on GitHub ↗

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