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.
What happens
CopilotCliBackend._parse_jsonl_responseinskillopt_sleep/backend.pyassumes thedatafield of anassistant.messageevent is an object:If
datais a string, a number, or a non-empty list, this raisesAttributeError. The exception escapes the per-linetry, which only wrapsjson.loads, so one bad line kills the whole parse and the backend returns nothing for that call.Repro
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_jsonexpects""and instead errors out. It builds a 2000 deep nested array expectingRecursionErrorfromjson.loads, but on Python 3.14 that parses fine, so it reaches the field access and hits theAttributeErrorinstead.Why this looks like drift rather than an intended contract
skillopt/model/copilot_backend.py::parse_copilot_jsonlparses the same event format and guards it:That guard arrived in 5497a31 ("harden and deduplicate JSONL parsing"), which deleted the exact
(obj.get("data") or {}).get("content")line from bothcopilot_backend.pyandcodex_harness.pyand merged the three copies into one helper.tests/test_copilot_exec_backend.pylocks the behavior in with a"data": "invalid"line mid stream, asserting the surrounding messages still concatenate.skillopt_sleep/backend.pypredates 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,
mainat 9c776fcHappy to send a PR that ports the guard and adds a focused test.