Description
Environment
- crewai 1.15.21 (CLI and library, from PyPI), Python 3.12.10, macOS Tahoe.
Summary
Running a crew via crewai run crashes the live TUI and cancels the in-progress run whenever an agent's streamed text output happens to contain a literal [...] (three dots inside square brackets). The crew's actual execution (LLM calls, tool calls) is unaffected; only the terminal renderer crashes, taking the whole session down with it.
Root cause
In crewai_cli/crew_run_tui.py, _format_json_in_text() scans streamed text for bracketed spans and passes each candidate to _try_parse_structured(). That function tries json.loads() first, then falls back to ast.literal_eval() for non-JSON text, accepting the result as long as it's a dict or list — without checking that its contents are JSON-serializable. ast.literal_eval("[...]") is valid Python and returns [Ellipsis] (bare ... is the Ellipsis singleton), which passes the isinstance(obj, (dict, list)) check. _format_json_in_text then calls json.dumps([Ellipsis], ...), which raises TypeError: Object of type ellipsis is not JSON serializable. This propagates up through _render_main_content → _tick and crashes the whole CrewRunApp, cancelling the run.
Steps to Reproduce
Minimal, deterministic repro (no LLM needed):
from crewai_cli.crew_run_tui import _format_json_in_text
_format_json_in_text("pandas,[...]")
# TypeError: Object of type ellipsis is not JSON serializable
Full CLI repro:
- Create any minimal crewai crew (one agent, one task).
- Give the task a description that reliably makes the model emit a literal [...], e.g.: "Describe an example Python list of a few package names followed by an ellipsis, formatted exactly like [numpy, pandas, ...]. Include that literal text in your response."
- Run crewai run.
- As the response streams into the TUI, _format_json_in_text hits the [...] substring, parses it to [Ellipsis], json.dumps raises, and the run is cancelled with Run cancelled.
Expected behavior
Expected my my crew to complete it's coding run with out failure.
Screenshots/Code snippets
from crewai_cli.crew_run_tui import _format_json_in_text
_format_json_in_text("pandas,[...]")
# TypeError: Object of type ellipsis is not JSON serializable
Operating System
Other (specify in additional context)
Python Version
3.12
crewAI Version
1.15.21
crewAI Tools Version
1.15.21
Virtual Environment
Venv
Evidence
╭───────────────────────────────────────────────────────────── Traceback (most recent call last) ──────────────────────────────────────────────────────────
│ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:1198 in _tick
│
│ 1195 │ │ │ with self._lock:
│ 1196 │ │ │ │ self._render_sidebar()
│ 1197 │ │ │ │ self._render_task_header()
│ ❱ 1198 │ │ │ │ self._render_main_content()
│ 1199 │ │ │ │ if self.query_one("#log-panel").display:
│ 1200 │ │ │ │ │ self._render_log_panel()
│ 1201 │ │ except NoMatches:
│
│ ╭──────────────────────────────────────────────────── locals ────────────────────────────────────────────────────╮
│ │ elapsed = 116.62594890594482 │
│ │ mins = 1 │
│ │ secs = 56 │
│ │ self = CrewRunApp(title='CrewAI — echo_eng_team', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'}) │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:1532 in _render_main_content
│
│ 1529 │ │
│ 1530 │ │ # Streaming output
│ 1531 │ │ if self._is_streaming and self._streaming_text:
│ ❱ 1532 │ │ │ text = self._filtered_streaming_text()
│ 1533 │ │ │ text = _unescape_text(text)
│ 1534 │ │ │ if text.strip():
│ 1535 │ │ │ │ lines = text.rstrip().split("\n")
│
│ ╭──────────────────────────────────────────────────────────────────────── locals ────────────────────────────────────────────────────────────────────────
│ │ _detail = ''
│ │ completed = False
│ │ desc = 'Compose the final migration plan document synthesizing findings from steps 1-8 a'+482
│ │ msg = 'Generating response…'
│ │ plan_title = "Investigate the echo project's current structure and dependency/packaging setup,"+198
│ │ self = CrewRunApp(title='CrewAI — echo_eng_team', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'})
│ │ short = 'Compose the final migration plan document synthesizing findings from steps 1-8 a'+11
│ │ should_scroll = False
│ │ sn = 9
│ │ st = 'pending'
│ │ step = {
│ │ │ 'step_number': 9,
│ │ │ 'description': 'Compose the final migration plan document synthesizing findings from steps 1-8 a'+482,
│ │ │ 'tool_to_use': 'file_writer_tool',
│ │ │ 'depends_on': [1, 2, 3, 4, 5, 6, 7, 8]
│ │ }
│ │ sty = 'yellow'
│ │ t = <text " PLAN\n ▸ Investigate the echo project's current structure and dependency/packaging setup,\n\n ✔ 1. List the files at the
│ │ root of the echo project directory to understand its layout. DONE wh…\n ✔ 2. Search the internet for the official UV documentation on
│ │ using UV for Python project/devel…\n ✔ 3. Search the internet for official documentation on UV Tools (uv tool install, uv tool
│ │ run/u…\n ⠸ 4. Based on step 1's listing, identify and list the contents of the source/package subdirecto…\n ⠸ 5. Read the current
│ │ Python packaging/config file(s) found in step 1 (e.g., pyproject.toml, se…\n ⠸ 6. Read the current dependency declaration file(s)
│ │ found in step 1 (e.g., requirements.txt, r…\n ○ 7. Read the project entry-point/CLI module identified in step 4 to determine console
│ │ script d…\n ⠸ 8. Read any existing developer/CI/build files found in step 1 (e.g., README, Makefile, tox.in…\n ○ 9. Compose the final
│ │ migration plan document synthesizing findings from steps 1-8 and researc…\n\n ⠸ Generating response…\n\n" [Span(0, 7, 'bold #666666'),
│ │ Span(7, 11, 'bold #1F7982'), Span(11, 92, 'bold #1F7982'), Span(93, 97, '#4aba6a'), Span(97, 192, '#666666'), Span(192, 196,
│ │ '#4aba6a'), Span(196, 291, '#666666'), Span(291, 295, '#4aba6a'), Span(295, 390, '#666666'), Span(390, 394, '#FF5A50'), Span(394, 489,
│ │ '#e0e0e0'), Span(489, 493, '#FF5A50'), Span(493, 588, '#e0e0e0'), Span(588, 592, '#FF5A50'), Span(592, 687, '#e0e0e0'), Span(687, 691,
│ │ '#666666'), Span(691, 786, '#666666'), Span(786, 790, '#FF5A50'), Span(790, 885, '#e0e0e0'), Span(885, 889, '#666666'), Span(889, 984,
│ │ '#666666'), Span(985, 989, '#FF5A50'), Span(989, 1011, '#AAAAAA')] ''>
│ │ widget = Static(id='main-content')
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│
│ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:1708 in _filtered_streaming_text
│
│ 1705 │ │ │ │ text = text[:plan_start].strip()
│ 1706 │ │
│ 1707 │ │ text = self._strip_step_observation_json(text)
│ ❱ 1708 │ │ return _format_json_in_text(text)
│ 1709 │
│ 1710 │ def _strip_step_observation_json(self, text: str) -> str:
│ 1711 │ │ """Hide structured step-observation JSON from the live transcript."""
│
│ ╭───────────────────────────────────────────────────── locals ──────────────────────────────────────────────────────╮
│ │ plan_start = -1 │
│ │ self = CrewRunApp(title='CrewAI — echo_eng_team', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'}) │
│ │ text = 'requires = ["hat≥3.1.43, pchling"], buildandas≥2.2.-backend = "hatch2, numpy≥'+1370 │ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:122 in _format_json_in_text │ │ 119 │ │ │ │ │ │ candidate = text[i : j + 1] │ 120 │ │ │ │ │ │ parsed = _try_parse_structured(candidate) │ 121 │ │ │ │ │ │ if parsed is not None: │ ❱ 122 │ │ │ │ │ │ │ formatted = _json.dumps( │ 123 │ │ │ │ │ │ │ │ parsed, indent=2, ensure_ascii=False │ 124 │ │ │ │ │ │ │ ) │ 125 │ │ │ │ │ │ │ result.append(formatted) │ │ ╭────────────────────────────────────────────── locals ───────────────────────────────────────────────╮ │ │ candidate = '[...]' │ │ │ close = ']' │ │ │ depth = 0 │ │ │ formatted = '[\n "hat≥3.1.43, pchling"\n]' │ │ │ i = 1420 │ │ │ j = 1424 │ │ │ parsed = [Ellipsis] │ │ │ result = ['', 'r', 'e', 'q', 'u', 'i', 'r', 'e', 's', ' ', ... +1388] │
│ │ text = 'requires = ["hat≥3.1.43, pchling"], `buildandas≥2.2.-backend = "hatch2, numpy≥'+1370 │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/init.py:238 in dumps
│
│ 235 │ │ skipkeys=skipkeys, ensure_ascii=ensure_ascii, ╭────────── locals ───────────╮
│ 236 │ │ check_circular=check_circular, allow_nan=allow_nan, indent=indent, │ allow_nan = True │
│ 237 │ │ separators=separators, default=default, sort_keys=sort_keys, │ check_circular = True │
│ ❱ 238 │ │ **kw).encode(obj) │ default = None │
│ 239 │ ensure_ascii = False │
│ 240 │ indent = 2 │
│ 241 _default_decoder = JSONDecoder(object_hook=None, object_pairs_hook=None) │ kw = {} │
│ │ obj = [Ellipsis] │
│ │ separators = None │
│ │ skipkeys = False │
│ │ sort_keys = False │
│ ╰─────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:202 in encode
│
│ 199 │ │ # equivalent to the PySequence_Fast that ''.join() would do.
│ 200 │ │ chunks = self.iterencode(o, _one_shot=True)
│ 201 │ │ if not isinstance(chunks, (list, tuple)):
│ ❱ 202 │ │ │ chunks = list(chunks)
│ 203 │ │ return ''.join(chunks)
│ 204 │
│ 205 │ def iterencode(self, o, _one_shot=False):
│
│ ╭───────────────────────────────────── locals ─────────────────────────────────────╮
│ │ chunks = <generator object _make_iterencode.._iterencode at 0x11a8f08e0> │
│ │ o = [Ellipsis] │
│ │ self = <json.encoder.JSONEncoder object at 0x11b31f080> │
│ ╰──────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:430 in _iterencode
│
│ 427 │ │ │ # see comment for int/float in _make_iterencode
│ 428 │ │ │ yield _floatstr(o)
│ 429 │ │ elif isinstance(o, (list, tuple)):
│ ❱ 430 │ │ │ yield from _iterencode_list(o, _current_indent_level)
│ 431 │ │ elif isinstance(o, dict):
│ 432 │ │ │ yield from _iterencode_dict(o, _current_indent_level)
│ 433 │ │ else:
│
│ ╭──────────────────────────────────────────────────── locals ────────────────────────────────────────────────────╮
│ │ _current_indent_level = 0 │
│ │ _default = <bound method JSONEncoder.default of <json.encoder.JSONEncoder object at 0x11b31f080>> │
│ │ _encoder = │
│ │ _intstr = <slot wrapper 'repr' of 'int' objects> │
│ │ id = │
│ │ isinstance = │
│ │ markers = {4744666816: [Ellipsis], 4406130592: Ellipsis} │
│ │ o = [Ellipsis] │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:326 in _iterencode_list
│
│ 323 │ │ │ │ │ chunks = _iterencode_dict(value, _current_indent_level)
│ 324 │ │ │ │ else:
│ 325 │ │ │ │ │ chunks = _iterencode(value, _current_indent_level)
│ ❱ 326 │ │ │ │ yield from chunks
│ 327 │ │ if newline_indent is not None:
│ 328 │ │ │ _current_indent_level -= 1
│ 329 │ │ │ yield '\n' + _indent * _current_indent_level
│
│ ╭──────────────────────────────────────────── locals ─────────────────────────────────────────────╮
│ │ _current_indent_level = 1 │
│ │ _encoder = │
│ │ _indent = ' ' │
│ │ _intstr = <slot wrapper 'repr' of 'int' objects> │
│ │ _item_separator = ',' │
│ │ buf = '[\n ' │
│ │ chunks = <generator object _make_iterencode.._iterencode at 0x11a8f01b0> │
│ │ first = False │
│ │ id = │
│ │ isinstance = │
│ │ lst = [Ellipsis] │
│ │ markerid = 4744666816 │
│ │ markers = {4744666816: [Ellipsis], 4406130592: Ellipsis} │
│ │ newline_indent = '\n ' │
│ │ separator = ',\n ' │
│ │ value = Ellipsis │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:439 in _iterencode
│
│ 436 │ │ │ │ if markerid in markers:
│ 437 │ │ │ │ │ raise ValueError("Circular reference detected")
│ 438 │ │ │ │ markers[markerid] = o
│ ❱ 439 │ │ │ o = _default(o)
│ 440 │ │ │ yield from _iterencode(o, _current_indent_level)
│ 441 │ │ │ if markers is not None:
│ 442 │ │ │ │ del markers[markerid]
│
│ ╭──────────────────────────────────────────────────── locals ────────────────────────────────────────────────────╮
│ │ _current_indent_level = 1 │
│ │ _default = <bound method JSONEncoder.default of <json.encoder.JSONEncoder object at 0x11b31f080>> │
│ │ _encoder = │
│ │ _intstr = <slot wrapper 'repr' of 'int' objects> │
│ │ id = │
│ │ isinstance = │
│ │ markerid = 4406130592 │
│ │ markers = {4744666816: [Ellipsis], 4406130592: Ellipsis} │
│ │ o = Ellipsis │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:180 in default
│
│ 177 │ │ │ │ return super().default(o) ╭──────────────────────── locals ─────────────────────────
│ 178 │ │ │ o = Ellipsis
│ 179 │ │ """ │ self = <json.encoder.JSONEncoder object at 0x11b31f080>
│ ❱ 180 │ │ raise TypeError(f'Object of type {o.class.name} ' ╰─────────────────────────────────────────────────────────
│ 181 │ │ │ │ │ │ f'is not JSON serializable')
│ 182 │
│ 183 │ def encode(self, o):
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
TypeError: Object of type ellipsis is not JSON serializable
/Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/asyncio/events.py:88: RuntimeWarning: The executor did not finishing joining its threads within 300 seconds.
self._context.run(self._callback, *self._args)
Run cancelled.
Possible Solution
In _try_parse_structured, either:
(a) validate that the ast.literal_eval result is recursively JSON-safe (only str/int/float/bool/None/list/dict) before accepting it, or
(b) wrap the json.dumps call in _format_json_in_text in a try/except and fall back to the unformatted original text on failure — a rendering hiccup shouldn't be able to cancel the underlying crew run at all.
Additional context
na
Description
Environment
Summary
Root cause
Steps to Reproduce
Minimal, deterministic repro (no LLM needed):
Full CLI repro:
Expected behavior
Expected my my crew to complete it's coding run with out failure.
Screenshots/Code snippets
Operating System
Other (specify in additional context)
Python Version
3.12
crewAI Version
1.15.21
crewAI Tools Version
1.15.21
Virtual Environment
Venv
Evidence
╭───────────────────────────────────────────────────────────── Traceback (most recent call last) ──────────────────────────────────────────────────────────
│ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:1198 in _tick
│
│ 1195 │ │ │ with self._lock:
│ 1196 │ │ │ │ self._render_sidebar()
│ 1197 │ │ │ │ self._render_task_header()
│ ❱ 1198 │ │ │ │ self._render_main_content()
│ 1199 │ │ │ │ if self.query_one("#log-panel").display:
│ 1200 │ │ │ │ │ self._render_log_panel()
│ 1201 │ │ except NoMatches:
│
│ ╭──────────────────────────────────────────────────── locals ────────────────────────────────────────────────────╮
│ │ elapsed = 116.62594890594482 │
│ │ mins = 1 │
│ │ secs = 56 │
│ │ self = CrewRunApp(title='CrewAI — echo_eng_team', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'}) │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:1532 in _render_main_content
│
│ 1529 │ │
│ 1530 │ │ # Streaming output
│ 1531 │ │ if self._is_streaming and self._streaming_text:
│ ❱ 1532 │ │ │ text = self._filtered_streaming_text()
│ 1533 │ │ │ text = _unescape_text(text)
│ 1534 │ │ │ if text.strip():
│ 1535 │ │ │ │ lines = text.rstrip().split("\n")
│
│ ╭──────────────────────────────────────────────────────────────────────── locals ────────────────────────────────────────────────────────────────────────
│ │ _detail = ''
│ │ completed = False
│ │ desc = 'Compose the final migration plan document synthesizing findings from steps 1-8 a'+482
│ │ msg = 'Generating response…'
│ │ plan_title = "Investigate the echo project's current structure and dependency/packaging setup,"+198
│ │ self = CrewRunApp(title='CrewAI — echo_eng_team', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'})
│ │ short = 'Compose the final migration plan document synthesizing findings from steps 1-8 a'+11
│ │ should_scroll = False
│ │ sn = 9
│ │ st = 'pending'
│ │ step = {
│ │ │ 'step_number': 9,
│ │ │ 'description': 'Compose the final migration plan document synthesizing findings from steps 1-8 a'+482,
│ │ │ 'tool_to_use': 'file_writer_tool',
│ │ │ 'depends_on': [1, 2, 3, 4, 5, 6, 7, 8]
│ │ }
│ │ sty = 'yellow'
│ │ t = <text " PLAN\n ▸ Investigate the echo project's current structure and dependency/packaging setup,\n\n ✔ 1. List the files at the
│ │ root of the echo project directory to understand its layout. DONE wh…\n ✔ 2. Search the internet for the official UV documentation on
│ │ using UV for Python project/devel…\n ✔ 3. Search the internet for official documentation on UV Tools (uv tool install, uv tool
│ │ run/u…\n ⠸ 4. Based on step 1's listing, identify and list the contents of the source/package subdirecto…\n ⠸ 5. Read the current
│ │ Python packaging/config file(s) found in step 1 (e.g., pyproject.toml, se…\n ⠸ 6. Read the current dependency declaration file(s)
│ │ found in step 1 (e.g., requirements.txt, r…\n ○ 7. Read the project entry-point/CLI module identified in step 4 to determine console
│ │ script d…\n ⠸ 8. Read any existing developer/CI/build files found in step 1 (e.g., README, Makefile, tox.in…\n ○ 9. Compose the final
│ │ migration plan document synthesizing findings from steps 1-8 and researc…\n\n ⠸ Generating response…\n\n" [Span(0, 7, 'bold #666666'),
│ │ Span(7, 11, 'bold #1F7982'), Span(11, 92, 'bold #1F7982'), Span(93, 97, '#4aba6a'), Span(97, 192, '#666666'), Span(192, 196,
│ │ '#4aba6a'), Span(196, 291, '#666666'), Span(291, 295, '#4aba6a'), Span(295, 390, '#666666'), Span(390, 394, '#FF5A50'), Span(394, 489,
│ │ '#e0e0e0'), Span(489, 493, '#FF5A50'), Span(493, 588, '#e0e0e0'), Span(588, 592, '#FF5A50'), Span(592, 687, '#e0e0e0'), Span(687, 691,
│ │ '#666666'), Span(691, 786, '#666666'), Span(786, 790, '#FF5A50'), Span(790, 885, '#e0e0e0'), Span(885, 889, '#666666'), Span(889, 984,
│ │ '#666666'), Span(985, 989, '#FF5A50'), Span(989, 1011, '#AAAAAA')] ''>
│ │ widget = Static(id='main-content')
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│
│ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:1708 in _filtered_streaming_text
│
│ 1705 │ │ │ │ text = text[:plan_start].strip()
│ 1706 │ │
│ 1707 │ │ text = self._strip_step_observation_json(text)
│ ❱ 1708 │ │ return _format_json_in_text(text)
│ 1709 │
│ 1710 │ def _strip_step_observation_json(self, text: str) -> str:
│ 1711 │ │ """Hide structured step-observation JSON from the live transcript."""
│
│ ╭───────────────────────────────────────────────────── locals ──────────────────────────────────────────────────────╮
│ │ plan_start = -1 │
│ │ self = CrewRunApp(title='CrewAI — echo_eng_team', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'}) │
│ │ text = '
requires = ["hat≥3.1.43, pchling"],buildandas≥2.2.-backend = "hatch2, numpy≥'+1370 │ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ /Users/duke/.local/share/uv/tools/crewai/lib/python3.12/site-packages/crewai_cli/crew_run_tui.py:122 in _format_json_in_text │ │ 119 │ │ │ │ │ │ candidate = text[i : j + 1] │ 120 │ │ │ │ │ │ parsed = _try_parse_structured(candidate) │ 121 │ │ │ │ │ │ if parsed is not None: │ ❱ 122 │ │ │ │ │ │ │ formatted = _json.dumps( │ 123 │ │ │ │ │ │ │ │ parsed, indent=2, ensure_ascii=False │ 124 │ │ │ │ │ │ │ ) │ 125 │ │ │ │ │ │ │ result.append(formatted) │ │ ╭────────────────────────────────────────────── locals ───────────────────────────────────────────────╮ │ │ candidate = '[...]' │ │ │ close = ']' │ │ │ depth = 0 │ │ │ formatted = '[\n "hat≥3.1.43, pchling"\n]' │ │ │ i = 1420 │ │ │ j = 1424 │ │ │ parsed = [Ellipsis] │ │ │ result = ['', 'r', 'e', 'q', 'u', 'i', 'r', 'e', 's', ' ', ... +1388] ││ │ text = '
requires = ["hat≥3.1.43, pchling"], `buildandas≥2.2.-backend = "hatch2, numpy≥'+1370 ││ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/init.py:238 in dumps
│
│ 235 │ │ skipkeys=skipkeys, ensure_ascii=ensure_ascii, ╭────────── locals ───────────╮
│ 236 │ │ check_circular=check_circular, allow_nan=allow_nan, indent=indent, │ allow_nan = True │
│ 237 │ │ separators=separators, default=default, sort_keys=sort_keys, │ check_circular = True │
│ ❱ 238 │ │ **kw).encode(obj) │ default = None │
│ 239 │ ensure_ascii = False │
│ 240 │ indent = 2 │
│ 241 _default_decoder = JSONDecoder(object_hook=None, object_pairs_hook=None) │ kw = {} │
│ │ obj = [Ellipsis] │
│ │ separators = None │
│ │ skipkeys = False │
│ │ sort_keys = False │
│ ╰─────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:202 in encode
│
│ 199 │ │ # equivalent to the PySequence_Fast that ''.join() would do.
│ 200 │ │ chunks = self.iterencode(o, _one_shot=True)
│ 201 │ │ if not isinstance(chunks, (list, tuple)):
│ ❱ 202 │ │ │ chunks = list(chunks)
│ 203 │ │ return ''.join(chunks)
│ 204 │
│ 205 │ def iterencode(self, o, _one_shot=False):
│
│ ╭───────────────────────────────────── locals ─────────────────────────────────────╮
│ │ chunks = <generator object _make_iterencode.._iterencode at 0x11a8f08e0> │
│ │ o = [Ellipsis] │
│ │ self = <json.encoder.JSONEncoder object at 0x11b31f080> │
│ ╰──────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:430 in _iterencode
│
│ 427 │ │ │ # see comment for int/float in _make_iterencode
│ 428 │ │ │ yield _floatstr(o)
│ 429 │ │ elif isinstance(o, (list, tuple)):
│ ❱ 430 │ │ │ yield from _iterencode_list(o, _current_indent_level)
│ 431 │ │ elif isinstance(o, dict):
│ 432 │ │ │ yield from _iterencode_dict(o, _current_indent_level)
│ 433 │ │ else:
│
│ ╭──────────────────────────────────────────────────── locals ────────────────────────────────────────────────────╮
│ │ _current_indent_level = 0 │
│ │ _default = <bound method JSONEncoder.default of <json.encoder.JSONEncoder object at 0x11b31f080>> │
│ │ _encoder = │
│ │ _intstr = <slot wrapper 'repr' of 'int' objects> │
│ │ id = │
│ │ isinstance = │
│ │ markers = {4744666816: [Ellipsis], 4406130592: Ellipsis} │
│ │ o = [Ellipsis] │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:326 in _iterencode_list
│
│ 323 │ │ │ │ │ chunks = _iterencode_dict(value, _current_indent_level)
│ 324 │ │ │ │ else:
│ 325 │ │ │ │ │ chunks = _iterencode(value, _current_indent_level)
│ ❱ 326 │ │ │ │ yield from chunks
│ 327 │ │ if newline_indent is not None:
│ 328 │ │ │ _current_indent_level -= 1
│ 329 │ │ │ yield '\n' + _indent * _current_indent_level
│
│ ╭──────────────────────────────────────────── locals ─────────────────────────────────────────────╮
│ │ _current_indent_level = 1 │
│ │ _encoder = │
│ │ _indent = ' ' │
│ │ _intstr = <slot wrapper 'repr' of 'int' objects> │
│ │ _item_separator = ',' │
│ │ buf = '[\n ' │
│ │ chunks = <generator object _make_iterencode.._iterencode at 0x11a8f01b0> │
│ │ first = False │
│ │ id = │
│ │ isinstance = │
│ │ lst = [Ellipsis] │
│ │ markerid = 4744666816 │
│ │ markers = {4744666816: [Ellipsis], 4406130592: Ellipsis} │
│ │ newline_indent = '\n ' │
│ │ separator = ',\n ' │
│ │ value = Ellipsis │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:439 in _iterencode
│
│ 436 │ │ │ │ if markerid in markers:
│ 437 │ │ │ │ │ raise ValueError("Circular reference detected")
│ 438 │ │ │ │ markers[markerid] = o
│ ❱ 439 │ │ │ o = _default(o)
│ 440 │ │ │ yield from _iterencode(o, _current_indent_level)
│ 441 │ │ │ if markers is not None:
│ 442 │ │ │ │ del markers[markerid]
│
│ ╭──────────────────────────────────────────────────── locals ────────────────────────────────────────────────────╮
│ │ _current_indent_level = 1 │
│ │ _default = <bound method JSONEncoder.default of <json.encoder.JSONEncoder object at 0x11b31f080>> │
│ │ _encoder = │
│ │ _intstr = <slot wrapper 'repr' of 'int' objects> │
│ │ id = │
│ │ isinstance = │
│ │ markerid = 4406130592 │
│ │ markers = {4744666816: [Ellipsis], 4406130592: Ellipsis} │
│ │ o = Ellipsis │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
│
│ /Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/json/encoder.py:180 in default
│
│ 177 │ │ │ │ return super().default(o) ╭──────────────────────── locals ─────────────────────────
│ 178 │ │ │ o = Ellipsis
│ 179 │ │ """ │ self = <json.encoder.JSONEncoder object at 0x11b31f080>
│ ❱ 180 │ │ raise TypeError(f'Object of type {o.class.name} ' ╰─────────────────────────────────────────────────────────
│ 181 │ │ │ │ │ │ f'is not JSON serializable')
│ 182 │
│ 183 │ def encode(self, o):
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
TypeError: Object of type ellipsis is not JSON serializable
/Users/duke/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/asyncio/events.py:88: RuntimeWarning: The executor did not finishing joining its threads within 300 seconds.
self._context.run(self._callback, *self._args)
Run cancelled.
Possible Solution
In _try_parse_structured, either:
(a) validate that the ast.literal_eval result is recursively JSON-safe (only str/int/float/bool/None/list/dict) before accepting it, or
(b) wrap the json.dumps call in _format_json_in_text in a try/except and fall back to the unformatted original text on failure — a rendering hiccup shouldn't be able to cancel the underlying crew run at all.
Additional context
na