Skip to content

Copilot backend in skillopt_sleep crashes when an assistant.message event has non-dict data #231

Description

@pravit-amp

What happens

CopilotCliBackend._parse_jsonl_response in skillopt_sleep/backend.py assumes the data field of an assistant.message event is an object:

content = (obj.get("data") or {}).get("content")

If data is a string, a number, or a non-empty list, this raises AttributeError. The exception escapes the per-line try, which only wraps json.loads, so one bad line kills the whole parse and the backend returns nothing for that call.

Repro

from skillopt_sleep.backend import CopilotCliBackend as B
B._parse_jsonl_response('{"type":"assistant.message","data":"hi"}')
# AttributeError: 'str' object has no attribute 'get'

data: [] happens to survive, because [] or {} falls back to {}. Only truthy non-dict values trip it.

Expected

The malformed line is skipped and the rest of the stream still parses.

An existing test already fails because of this

tests/test_sleep_engine.py::TestCopilotBackend::test_parse_jsonl_ignores_excessively_nested_json expects "" and instead errors out. It builds a 2000 deep nested array expecting RecursionError from json.loads, but on Python 3.14 that parses fine, so it reaches the field access and hits the AttributeError instead.

Why this looks like drift rather than an intended contract

skillopt/model/copilot_backend.py::parse_copilot_jsonl parses the same event format and guards it:

data = obj.get("data")
if not isinstance(data, dict):
    continue

That guard arrived in 5497a31 ("harden and deduplicate JSONL parsing"), which deleted the exact (obj.get("data") or {}).get("content") line from both copilot_backend.py and codex_harness.py and merged the three copies into one helper. tests/test_copilot_exec_backend.py locks the behavior in with a "data": "invalid" line mid stream, asserting the surrounding messages still concatenate.

skillopt_sleep/backend.py predates that commit and was not part of the consolidation, most likely because the sleep package intentionally keeps zero dependency on the research package.

Environment

macOS 15, Python 3.14.6, main at 9c776fc

Happy to send a PR that ports the guard and adds a focused test.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions