{"record":{"id":"7810f0b32727ab91","repo":"odysseus-dev/odysseus","slug":"server-did-not-return-a-session-id","errorCode":null,"errorMessage":"Server did not return a session id","messagePattern":"Server did not return a session id","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/visual_report.py","lineNumber":1096,"sourceCode":"  if (chatBtn) {{\n    chatBtn.addEventListener('click', function() {{\n      var researchId = chatBtn.dataset.researchId;\n      if (!researchId) return;\n      var origLabel = chatBtn.innerHTML;\n      chatBtn.disabled = true;\n      chatBtn.innerHTML = '<span>Creating chat…</span>';\n      fetch('/api/research/spinoff/' + encodeURIComponent(researchId), {{\n        method: 'POST', credentials: 'same-origin',\n      }}).then(function(res) {{\n        if (!res.ok) {{\n          return res.json().then(function(d) {{\n            throw new Error(d && d.detail ? d.detail : ('HTTP ' + res.status));\n          }}, function() {{ throw new Error('HTTP ' + res.status); }});\n        }}\n        return res.json();\n      }}).then(function(data) {{\n        if (!data || !data.session_id) {{\n          throw new Error('Server did not return a session id');\n        }}\n        var url = '/#' + data.session_id;\n        var opened = false;\n        // The report typically opens in a new tab — if we have access to the\n        // original Odysseus tab, navigate it and close this report tab so the\n        // user lands directly in the new chat.\n        try {{\n          if (window.opener && !window.opener.closed) {{\n            window.opener.location.href = url;\n            window.opener.location.reload();\n            window.opener.focus();\n            opened = true;\n            window.close();\n          }}\n        }} catch (e) {{ /* cross-origin or detached opener — fall through */ }}\n        if (!opened) {{\n          // No opener (report was opened directly via URL) — open the chat in a\n          // new tab so the report stays available.","sourceCodeStart":1078,"sourceCodeEnd":1114,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/src/visual_report.py#L1078-L1114","documentation":"Raised by _fetch_bytes when a downloaded file's content length exceeds MAX_FILE_BYTES (400,000 bytes, ~400 KB). Every raw-asset fetch in the importer — SKILL.md itself, referenced files, and per-file downloads during directory traversal — goes through this single guard, so any one oversized file aborts the whole import.","triggerScenarios":"A skill folder contains a single text asset (large CSV dataset, bundled JSON, long reference doc, minified JS) larger than 400 KB; fetch_skill_bundle downloads its download_url via _fetch_bytes and len(r.content) > 400000. Directly fetching an oversized SKILL.md from a /blob/... link hits the same check.","commonSituations":"Skills that bundle prompt libraries, embeddings, dictionaries, or generated reference data inline instead of linking out. Also common after a doc-generation step adds a huge auto-produced file next to SKILL.md.","solutions":["Move the oversized asset out of the skill folder and reference it by URL inside SKILL.md instead of shipping it in the bundle","Split the large file into smaller pieces under 400 KB each, or trim it to what the skill actually needs","If you control the importer and genuinely need bigger files, raise MAX_FILE_BYTES in services/memory/skill_importer.py:21 — but note the 2 MB MAX_TOTAL_BYTES bundle cap still applies","Check for accidentally committed build artifacts (dist/, minified bundles, lock files) and remove them from the repo path"],"exampleFix":"# before\nmy-skill/\n  SKILL.md\n  big-dataset.json   # 1.2 MB -> SkillImportError: file too large\n\n# after\nmy-skill/\n  SKILL.md           # references https://example.com/data/big-dataset.json","handlingStrategy":"validation","validationCode":"import httpx\nfrom services.memory.skill_importer import MAX_FILE_BYTES\n\nasync def fetch_if_small(url: str) -> bytes | None:\n    async with httpx.AsyncClient(follow_redirects=True) as c:\n        r = await c.head(url, follow_redirects=True)\n        size = int(r.headers.get(\"content-length\") or 0)\n        if size and size > MAX_FILE_BYTES:\n            return None  # skip before downloading\n        r = await c.get(url)\n        return r.content if len(r.content) <= MAX_FILE_BYTES else None","typeGuard":null,"tryCatchPattern":"try:\n    files, src = fetch_skill_bundle(url)\nexcept SkillImportError as e:\n    if \"file too large\" in str(e):\n        report(f\"{url}: an asset exceeds {MAX_FILE_BYTES} bytes — slim the skill folder or link assets by URL\")\n        return None\n    raise","preventionTips":["Keep every bundled text asset under 400 KB; link large data by URL in SKILL.md","Add a CI check on skill repos rejecting files > MAX_FILE_BYTES in skill folders","HEAD-check content-length before downloading when building custom fetchers"],"tags":["size-limit","download","skill-import","github"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}