unslothai/unsloth · error · ValueError
{path}: not valid JSON: {exc}
Error message
{path}: not valid JSON: {exc} What it means
Thrown by streamResearchEvents (research-api.ts:144-208) when the POST to /api/chat/research-runs/{id}/events returns response.ok === true but response.body is null/undefined. A 200 response to a text/event-stream request must always carry a body; a null body means the fetch stack (service worker, polyfill, HTTP/1.0 proxy, or a mocked fetch) did not expose streaming, so the SSE reader cannot be constructed.
Source
Thrown at scripts/check_new_install_scripts.py:123
)
if lifecycle:
ver = entry.get("version") or "<unversioned>"
seen[f"{name}@{ver}"] = name
_walk_v1(entry.get("dependencies"), depth = depth + 1)
if version == 1 or "dependencies" in lock:
_walk_v1(lock.get("dependencies") or {})
return seen
def _load_lockfile(path: Path) -> dict:
if not path.exists():
raise FileNotFoundError(f"lockfile not found: {path}")
try:
return json.loads(path.read_text(encoding = "utf-8"))
except json.JSONDecodeError as exc:
raise ValueError(f"{path}: not valid JSON: {exc}") from exc
# Registry lookup for the postinstall command body (best-effort).
def _fetch_registry_scripts(name: str, version: str) -> dict[str, str] | None:
"""Return {hook: command} for lifecycle hooks in registry metadata; None on any error (never raises)."""
safe_name = urllib.parse.quote(name, safe = "@/")
url = f"{REGISTRY_BASE}{safe_name}/{urllib.parse.quote(version)}"
try:
with urllib.request.urlopen(url, timeout = REGISTRY_TIMEOUT_SECS) as resp:
body = resp.read()
except (urllib.error.URLError, OSError, ValueError, TimeoutError):
return None
try:
meta = json.loads(body)
except json.JSONDecodeError:
return NoneView on GitHub (pinned to 203007d190)
Solutions
- Hard-reload with service workers disabled (DevTools > Application > Bypass for network) to rule out a synthesized Response.
- Test in a current Chrome/Firefox; if the error follows the browser, the fetch implementation lacks streaming support.
- Check whether a proxy between client and backend buffers SSE; configure it to stream (disable response buffering for text/event-stream).
- In tests, mock fetch with a Response whose body is a ReadableStream (e.g. new Response(sseChunks.join(''), {headers:{'content-type':'text/event-stream'}})).
Example fix
// before (test mock)
fetchMock.mockResolvedValue(new Response());
// after — give the mock a real streaming body
fetchMock.mockResolvedValue(new Response('id:1\nevent:status\ndata:{"run":{}}\n\n', { headers: { 'content-type': 'text/event-stream' } })); Defensive patterns
Strategy: type-guard
Validate before calling
async function probeEventStreamSupport(): Promise<boolean> {
const res = await fetch('/api/health', { method: 'POST' });
return typeof res.body?.getReader === 'function';
} Type guard
function hasStreamBody(response: Response): response is Response & { body: ReadableStream<Uint8Array> } {
return response.body instanceof ReadableStream;
} Try / catch
try {
for await (const event of streamResearchEvents(id, after, signal)) { handle(event); }
} catch (error) {
if (error instanceof Error && error.message === "Research event stream returned no response body") {
// fetch stack cannot stream: disable live research updates or reload without service worker
} else throw error;
} Prevention
- Bypass service workers for /api/chat/research-runs/*/events requests.
- In tests, always mock fetch with a Response built from a real stream/string body.
- Feature-detect ReadableStream support before subscribing UI to SSE panels.
When it happens
Trigger: POST /api/chat/research-runs/{id}/events?after=N with accept: text/event-stream succeeding at the HTTP layer but yielding no stream — e.g. a service worker or older browser fetch polyfill returning a synthesized Response without a body, a proxy that buffers the whole stream and closes it oddly, or test mocks that return new Response() with no body argument.
Common situations: Running the studio frontend under an aggressive PWA/service-worker cache; older Safari or embedded webviews with incomplete streaming fetch; e2e tests mocking fetch without a ReadableStream; corporate proxies that strip transfer-encoding.
Related errors
- Stream response missing body
- lockfile not found: {path}
- deadline reached before {method} {_redact_url(url)}
- ChatGPT returned a malformed stream.
- ChatGPT stream ended before completion.
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/21c63fe3521d5e3b.
Report an issue: GitHub.