Claude Agent SDKclaude-agent-sdk 0.2.157 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
46 small wins to finish your pathNext lesson →

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:

Example
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)
  • The underlying failure is an ordinary FileNotFoundError, and the SDK re-raises it as a CLINotFoundError with 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_path the SDK searches your PATH and 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 the claude executable; and if you rely on PATH, confirm claude --version works 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:

Example
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)

This 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 .bat or .cmd file, including the claude.cmd shim an npm install leaves behind. The refusal is deliberate hardening, because Windows runs batch files through cmd.exe, which re-parses the command line and can execute commands injected through an argument. The fix is a native claude.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:

Example
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)
  • The exception carries the offending line on a line attribute and the original decode error on original_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_size option bounds one message, and passing it raises CLIJSONDecodeError with 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 CLINotFoundError and CLIConnectionError only. 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.

python
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

ErrorWhat it tells youWhere to look
CLINotFoundErrorNo claude executable at the path, or none found on PATHThe path in the message; claude --version in the same environment
CLIConnectionError: working directorycwd points at a directory that is not thereThe cwd option
CLIConnectionError: failed to startThe file exists and cannot be runExecute permission, file type, and the architecture of a bundled binary
CLIConnectionError: batch scriptThe resolved path is a Windows .cmd or .bat shimInstall a native claude.exe
CLIJSONDecodeErrorA line that began like JSON did not parse, or one message was too largeWhat the process writes to standard output; max_buffer_size
success with no structured_outputThe loop finished and nothing passed the schemaThe 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 PATH from 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 claude on the path is the batch shim the SDK refuses to run.
Watch out. These errors are raised before the loop starts, so none of them yields a result message. A handler written around the result message, reading its subtype to decide whether the run worked, never sees a startup failure at all. Catch CLIConnectionError around the iteration itself, and keep the result-message check for what happens after the session is alive.
Try it yourself
  • Change cli_path to 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 hello instead of a broken JSON line, and watch the line be skipped rather than raised.
  • Catch CLIConnectionError around the first example and print one sentence a user could act on instead of the traceback.

This is what real progress feels like.