When the CLI will not start
CLINotFoundError is the error the SDK raises when it cannot find a claude executable to start, and it is the first of a small family of failures that happen before any message arrives, while the SDK is still trying to launch the Claude Code process.
Last updated: 20 Oct, 2026 · Claude Agent SDK
Every run so far handed query() a transport, so no process was ever started. Leave transport out, as a real agent does, and there is a subprocess to launch, a path to resolve and credentials to find. This lesson causes three of those failures for real and reads what comes back.
Pointing cli_path at a file that is not there
The cli_path option tells the SDK where the binary is. Give it a path with nothing at the end of it:
from claude_agent_sdk import ClaudeAgentOptions, query
options = ClaudeAgentOptions(cli_path="/usr/local/bin/claude-code")
async for message in query(prompt="Where is my order A17?", options=options):
print(message)FileNotFoundError: [Errno 2] No such file or directory: '/usr/local/bin/claude-code'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 4, in <module>
async for message in query(prompt="Where is my order A17?", options=options):
claude_agent_sdk._errors.CLINotFoundError: Claude Code not found at: /usr/local/bin/claude-code- The underlying failure is an ordinary
FileNotFoundError, and the SDK re-raises it as aCLINotFoundErrorwith the path in the message, which is why both appear in the traceback. - The message names the path you configured. The troubleshooting page says that without
cli_paththe SDK searches yourPATHand the common install locations instead, and the message then carries install instructions for your platform. - Nothing was yielded before it. The error comes from
connect(), so there is no result message to inspect and no cost to account for. - The three fixes the documentation gives: install Claude Code if it is missing; if you set
cli_path, confirm the file exists and is theclaudeexecutable; and if you rely onPATH, confirmclaude --versionworks in the same environment the application runs in.
Giving the session a working directory that does not exist
The same launch step resolves cwd, and a missing directory fails there too. The SDK separates the two cases by checking the directory itself:
from claude_agent_sdk import ClaudeAgentOptions, query
options = ClaudeAgentOptions(cwd="/no/such/place")
async for message in query(prompt="Where is my order A17?", options=options):
print(message)FileNotFoundError: [Errno 2] No such file or directory: '/no/such/place'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 4, in <module>
async for message in query(prompt="Where is my order A17?", options=options):
claude_agent_sdk._errors.CLIConnectionError: Working directory does not exist: /no/such/placeThis one is a CLIConnectionError, the parent class of CLINotFoundError, so except CLIConnectionError catches both and the message is what tells them apart. The troubleshooting page documents three more messages under the same class:
Failed to start Claude Code: ..., where the rest of the message is the operating system's own error. The usual cause is a resolved path that points at something that cannot run: a text file, a directory, or a file with no execute permission.Refusing to execute batch script ..., on Windows, when the resolved path is a.bator.cmdfile, including theclaude.cmdshim an npm install leaves behind. The refusal is deliberate hardening, because Windows runs batch files throughcmd.exe, which re-parses the command line and can execute commands injected through an argument. The fix is a nativeclaude.exe.Not connected. Call connect() first., which comes from using the client class before it has connected. A conversation with ClaudeSDKClient shows the two ways to avoid it.
Feeding the SDK a line it cannot decode
The SDK and Claude Code speak newline-delimited JSON, one message per line. A line that begins with { and then fails to parse raises CLIJSONDecodeError. A shell script standing in for the binary is enough to produce one on macOS or Linux:
import os
from claude_agent_sdk import ClaudeAgentOptions, query
# a fake binary that writes one truncated JSON line and exits
with open("fake-claude", "w") as handle:
handle.write("#!/bin/sh\nprintf '{\"type\": \"result\"\\n'\n")
os.chmod("fake-claude", 0o755)
options = ClaudeAgentOptions(cli_path=os.path.abspath("fake-claude"))
async for message in query(prompt="Where is my order A17?", options=options):
print(message)json.decoder.JSONDecodeError: Expecting ',' delimiter: line 1 column 18 (char 17)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 11, in <module>
async for message in query(prompt="Where is my order A17?", options=options):
claude_agent_sdk._errors.CLIJSONDecodeError: Failed to decode JSON: {"type": "result"...- The exception carries the offending line on a
lineattribute and the original decode error onoriginal_error, and the message shows the first hundred characters of the line. - A line that does not start with
{is skipped instead, not raised, so a binary that prints a banner or a warning to standard output does not break the session. - The same error covers an oversized message. The
max_buffer_sizeoption bounds one message, and passing it raisesCLIJSONDecodeErrorwith a message about the buffer size rather than about JSON. - This one is not on the troubleshooting page. The class is exported by the installed package and raised by its subprocess transport, and the documentation's startup section covers
CLINotFoundErrorandCLIConnectionErroronly. Where the two disagree, what the package does is what happens.
A success that carries no structured output
The last failure in this family is the one that does not look like a failure. A session asked for structured output can end with the subtype success while structured_output is None: the run completed, and no validated output exists. The documentation's example of how that happens is a schema no output can satisfy, such as conflicting length constraints.
if isinstance(message, ResultMessage):
# a success with no validated output is a failure for your code
if message.subtype == "success" and message.structured_output is not None:
use(message.structured_output)
else:
report(message.subtype, message.errors)That check is shown rather than run: producing the case needs a real Claude Code session with an output schema attached, which is a key and a bill. Structured output sets the schema up; the rule to carry from here is to treat a success with no output as a failure, and to check both fields before using either.
Which startup error means what
| Error | What it tells you | Where to look |
|---|---|---|
CLINotFoundError | No claude executable at the path, or none found on PATH | The path in the message; claude --version in the same environment |
CLIConnectionError: working directory | cwd points at a directory that is not there | The cwd option |
CLIConnectionError: failed to start | The file exists and cannot be run | Execute permission, file type, and the architecture of a bundled binary |
CLIConnectionError: batch script | The resolved path is a Windows .cmd or .bat shim | Install a native claude.exe |
CLIJSONDecodeError | A line that began like JSON did not parse, or one message was too large | What the process writes to standard output; max_buffer_size |
success with no structured_output | The loop finished and nothing passed the schema | The schema, simplified until output validates |
Where these errors show up in a deployment
- A service or an IDE starting your program, where the process runs with a different
PATHfrom your shell, so the binary your terminal finds is invisible to the service. The troubleshooting page calls this out by name. - A container image, where the bundled binary does not match the image's architecture or its C library, or lost its execute permission during the build. Reinstalling the SDK inside the image build is the documented fix.
- A Windows machine with an npm install of Claude Code, where the only
claudeon the path is the batch shim the SDK refuses to run.
CLIConnectionError around the iteration itself, and keep the result-message check for what happens after the session is alive.Related
- Previous: Streaming input
- Next: The built-in tools
- See also: Hosting the SDK
- Reference: Troubleshoot the Agent SDK
- Change
cli_pathto a directory you know exists and read how the message changes from a missing file to a failure to launch. - Make the fake binary print
helloinstead of a broken JSON line, and watch the line be skipped rather than raised. - Catch
CLIConnectionErroraround the first example and print one sentence a user could act on instead of the traceback.
This is what real progress feels like.