Skip to content

Error context destroyed through exception flattening — CLI failures produce unhelpful 'Command failed with exit code 1' #798

@itsnotatbug

Description

@itsnotatbug

SDK version

claude-agent-sdk==0.1.56

Description

When the Claude CLI subprocess exits with a non-zero code, the SDK destroys all error context through a 3-step chain, making failures impossible to diagnose:

  1. _internal/transport/subprocess_cli.py:614ProcessError is created with stderr="Check stderr output for details" (hardcoded string, not actual stderr from the process)
  2. _internal/query.py:250 — Caught in _read_task, flattened via str(e), sent as {"type": "error", "error": str(e)} through the message stream
  3. _internal/query.py:726receive_messages() re-raises as bare Exception(message.get("error")), losing the ProcessError type entirely

Consumer code that catches ClaudeSDKError never fires because the exception arrives as bare Exception.

Reproduction

Any scenario where the Claude CLI process exits with code 1 (permission denied, internal error, bad config). The consumer receives:

Exception: Command failed with exit code 1 (exit code: 1)
Error output: Check stderr output for details

No actual stderr, no typed exception, no actionable detail.

Issues found (in order of severity)

File Line Problem
subprocess_cli.py 616 stderr="Check stderr output for details" is hardcoded — actual stderr is never captured into the error
query.py 250 str(e) flattens ProcessError (which has exit_code/stderr attrs) into a plain string
query.py 726 raise Exception(...) instead of preserving/re-raising original exception type — except ClaudeSDKError in consumer code never triggers
query.py 207-209 Control request errors also create bare Exception from dict
subprocess_cli.py 446-449 stderr reading exceptions silently swallowed (except Exception: pass)
subprocess_cli.py 608 process.wait() failure caught as returncode = -1, no logging
query.py 272, 318, 334, 346, 385, 420 Bare Exception() instead of typed ClaudeSDKError subclasses

Expected behavior

  1. ProcessError.stderr should contain the actual stderr output from the CLI process, not a hardcoded string
  2. query.py:726 should raise ProcessError (or at minimum ClaudeSDKError), not bare Exception — so consumer except ClaudeSDKError blocks work
  3. query.py:250 should preserve the exception object (or at least its type and attributes) rather than stringifying it

Workaround

Consumer code must catch Exception (not ClaudeSDKError) and try getattr(exc, "exit_code", None) defensively. But even that doesn't help here because the attributes are stripped at step 2 before re-raising.

Environment

  • Python 3.14.3
  • claude-agent-sdk 0.1.56
  • Windows 11
  • Claude Code CLI installed via npm

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions