Skip to content

SDK MCP tools unavailable when resuming with empty streaming input (regression in 0.2.140) #1226

Description

@ncoop57

Resuming a session with a pending deferred tool fails when the tool comes from an SDK MCP server and the resume passes an empty streaming input. Claude Code reports the tool as unavailable. It never re-invokes the tool. Passing a text prompt instead works.

0.2.139 works. 0.2.140 and later fail.

import anyio, dataclasses, tempfile
from claude_agent_sdk import (ClaudeAgentOptions, HookMatcher, ResultMessage,
                              create_sdk_mcp_server, query, tool)

ALLOW = []

@tool("flux", "Read the flux", {})
async def flux(args): return {"content": [{"type": "text", "text": "41.7 gauss"}]}

async def gate(inp, tool_use_id, ctx):
    dec = "allow" if ALLOW else "defer"
    return {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": dec}}

async def no_prompt():
    return
    yield

opts = ClaudeAgentOptions(
    model="haiku", cwd=tempfile.mkdtemp(), setting_sources=[],
    mcp_servers={"probe": create_sdk_mcp_server("probe", tools=[flux])},
    allowed_tools=["mcp__probe__flux"],
    hooks={"PreToolUse": [HookMatcher(matcher="mcp__probe__flux", hooks=[gate])]})

async def run(prompt, **kw):
    async for m in query(prompt=prompt, options=dataclasses.replace(opts, **kw)):
        if isinstance(m, ResultMessage): return m

async def main():
    a = await run("Measure the flux.")
    print("leg 1:", a.stop_reason)
    ALLOW.append(True)
    b = await run(no_prompt(), resume=a.session_id)
    print("leg 2:", b.stop_reason, "| is_error:", b.is_error)

anyio.run(main)

Leg 1 defers the tool and ends with tool_deferred. Leg 2 resumes the session to re-invoke the deferred call. Output:

0.2.139  leg 2: end_turn                  is_error: False
0.2.140  leg 2: tool_deferred_unavailable is_error: True

On 0.2.140 the run also prints:

Task exception was never retrieved
future: <Task finished coro=<Query._handle_control_request() ...>
         exception=CLIConnectionError('ProcessTransport is not ready for writing')>
  File ".../claude_agent_sdk/_internal/query.py", line 580, in _handle_control_request
    await self.transport.write(json.dumps(success_response) + "\n")
  File ".../transport/subprocess_cli.py", line 1048, in write
    raise CLIConnectionError("ProcessTransport is not ready for writing")

Claude Code logs Deferred tool resume: tool '...' is no longer available (MCP server disconnected or tool removed). The failing run makes no API call. duration_api_ms is 0 and token usage is zero.

Other results:

  • Replacing no_prompt() with a text prompt works on every version.
  • Deferring a built-in tool such as Read the same way resumes correctly. Only SDK MCP tools fail.
  • The Claude Code version does not matter. On 0.2.139 the resume works against CLI 2.1.232, 2.1.235 and 2.1.237. On 0.2.142 it fails against all three.

I have not confirmed the mechanism. The traceback shows the SDK failing to answer a control request from Claude Code after the input stream ends.

Environment: Claude Code 2.1.237, Python 3.13, Linux.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions