{"record":{"id":"181e409f7217f233","repo":"unslothai/unsloth","slug":"lockfile-not-found-path","errorCode":null,"errorMessage":"lockfile not found: {path}","messagePattern":"lockfile not found: (.+?)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"scripts/check_new_install_scripts.py","lineNumber":119,"sourceCode":"            scripts = entry.get(\"scripts\") or {}\n            lifecycle = any(\n                isinstance(scripts, dict) and scripts.get(hook)\n                for hook in (\"preinstall\", \"install\", \"postinstall\")\n            )\n            if lifecycle:\n                ver = entry.get(\"version\") or \"<unversioned>\"\n                seen[f\"{name}@{ver}\"] = name\n            _walk_v1(entry.get(\"dependencies\"), depth = depth + 1)\n\n    if version == 1 or \"dependencies\" in lock:\n        _walk_v1(lock.get(\"dependencies\") or {})\n\n    return seen\n\n\ndef _load_lockfile(path: Path) -> dict:\n    if not path.exists():\n        raise FileNotFoundError(f\"lockfile not found: {path}\")\n    try:\n        return json.loads(path.read_text(encoding = \"utf-8\"))\n    except json.JSONDecodeError as exc:\n        raise ValueError(f\"{path}: not valid JSON: {exc}\") from exc\n\n\n# Registry lookup for the postinstall command body (best-effort).\n\n\ndef _fetch_registry_scripts(name: str, version: str) -> dict[str, str] | None:\n    \"\"\"Return {hook: command} for lifecycle hooks in registry metadata; None on any error (never raises).\"\"\"\n    safe_name = urllib.parse.quote(name, safe = \"@/\")\n    url = f\"{REGISTRY_BASE}{safe_name}/{urllib.parse.quote(version)}\"\n    try:\n        with urllib.request.urlopen(url, timeout = REGISTRY_TIMEOUT_SECS) as resp:\n            body = resp.read()\n    except (urllib.error.URLError, OSError, ValueError, TimeoutError):\n        return None","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/scripts/check_new_install_scripts.py#L101-L137","documentation":"ResearchApiError thrown by the shared json() helper in research-api.ts:49-65 when a /api/chat/research-runs request fails and the error body contains neither a string `detail` nor a string `message`. Unlike a plain Error, it carries the HTTP status on error.status so callers (e.g. isPermanentResearchError at research-api.ts:216) can distinguish retryable 5xx from permanent 4xx.","triggerScenarios":"POST /api/chat/research-runs, or any mutate() call, or the streamResearchEvents !response.ok path returning an error whose body is empty, null (json parse failed), or has non-string detail — e.g. a 500 with an empty body, a 422 with a structured (array/object) validation detail, or a proxied HTML error page.","commonSituations":"Backend research worker crashed mid-run (500 with no detail); misconfigured reverse proxy returning 502 HTML; FastAPI validation errors where detail is a list of objects rather than a string; expired auth yielding a non-JSON 401.","solutions":["Inspect error.status in a catch: if >= 500 treat as transient and retry via the existing reconnect logic; if 4xx fix the request payload.","Reproduce the failing request in the Network tab and read the raw response body to find the server-side cause.","If auth-related (401), re-login and retry.","As a developer, stringify non-string detail values (FastAPI validation arrays) before falling back to the generic message."],"exampleFix":"// before\nconst detail = (body as { detail?: unknown })?.detail;\n\n// after — stringify FastAPI structured validation details\nconst detail = body?.detail;\nconst detailText = typeof detail === \"string\" ? detail : detail ? JSON.stringify(detail) : undefined;","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isResearchApiError(e: unknown): e is ResearchApiError {\n  return e instanceof Error && e.name === \"ResearchApiError\" && typeof (e as ResearchApiError).status === \"number\";\n}","tryCatchPattern":"try {\n  await createResearchRun(input);\n} catch (error) {\n  if (isResearchApiError(error)) {\n    if (error.status >= 500) { /* transient: backoff and retry */ }\n    else { /* permanent 4xx: fix payload, do not retry */ }\n  } else throw error;\n}","preventionTips":["Validate request payloads against the CreateResearchRunInput shape before posting to avoid 422s.","Use error.status (set on ResearchApiError) rather than parsing the message string.","Mirror the file's own isPermanentResearchError pattern for any new call sites."],"tags":["http","api","research","fastapi","frontend"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}