{"record":{"id":"ed123dfce96ca9e0","repo":"odysseus-dev/odysseus","slug":"d-detail-failed","errorCode":null,"errorMessage":"d.detail || 'Failed'","messagePattern":"d\\.detail \\|\\| 'Failed'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/admin.js","lineNumber":2580,"sourceCode":"      };\n      input.addEventListener('blur', commit);\n      input.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); input.blur(); } });\n    });\n    // Scope toggle change → PATCH the whole scopes array for this token.\n    list.querySelectorAll('.adm-tok-scope').forEach(cb => {\n      cb.addEventListener('change', async () => {\n        const tokenId = cb.dataset.tokenId;\n        const panel = list.querySelector(`[data-adm-tok-perm=\"${tokenId}\"]`);\n        const msg = list.querySelector(`.adm-tok-scope-msg[data-token-id=\"${tokenId}\"]`);\n        const scopes = Array.from(panel.querySelectorAll('.adm-tok-scope:checked')).map(input => input.dataset.scope);\n        try {\n          const r = await fetch(`/api/tokens/${tokenId}`, {\n            method: 'PATCH', credentials: 'same-origin',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ scopes }),\n          });\n          const d = await r.json().catch(() => ({}));\n          if (!r.ok) throw new Error(d.detail || 'Failed');\n          if (msg) { msg.textContent = 'Saved'; msg.style.color = 'var(--green, #50fa7b)'; setTimeout(() => { msg.textContent = ''; }, 1200); }\n        } catch (err) {\n          cb.checked = !cb.checked;\n          if (msg) { msg.textContent = (err && err.message) || 'Failed'; msg.style.color = 'var(--red)'; }\n        }\n      });\n    });\n  } catch (e) { list.innerHTML = '<div class=\"admin-error\">Failed to load tokens</div>'; }\n}\n\nfunction initTokenForm() {\n  const addBtn = el('adm-tokenAddBtn');\n  if (!addBtn || addBtn.dataset.bound) return;\n  addBtn.dataset.bound = '1';\n  addBtn.addEventListener('click', async () => {\n    const msg = el('adm-tokenMsg');\n    const reveal = el('adm-tokenReveal');\n    msg.textContent = ''; msg.className = ''; reveal.style.display = 'none';","sourceCodeStart":2562,"sourceCodeEnd":2598,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/admin.js#L2562-L2598","documentation":"Raised inside _list_github_dir when the cumulative UTF-8 byte size of all fetched bundle files exceeds MAX_TOTAL_BYTES (2,000,000 bytes, ~2 MB). Each downloaded file's size is added to a running total; crossing the limit aborts the import mid-traversal. This is a bundle-wide cap, distinct from the per-file MAX_FILE_BYTES (400 KB).","triggerScenarios":"A skill folder whose text files sum to more than 2 MB: e.g. 8 reference docs of 300 KB each (each passes the per-file check), or a deep tree of many files where total crosses 2 MB before the MAX_FILES=64 cap stops traversal.","commonSituations":"Monorepos where the importer walks a large directory because the linked path contains many siblings; skills bundling full documentation sets, changelogs, or generated API references alongside SKILL.md.","solutions":["Slim the skill folder: keep SKILL.md plus only essential assets under 2 MB total, link everything else by external URL","Link the exact subfolder containing SKILL.md instead of a parent folder, so traversal does not pull in sibling content","Move large reference material to a docs/ folder outside the skill path or host it separately","If the limit is genuinely too low for your use case, raise MAX_TOTAL_BYTES in services/memory/skill_importer.py:20 (coordinate with memory constraints of the skill store)"],"exampleFix":"# before\nurl = \"https://github.com/acme/mono/tree/main\"          # walks whole repo, >2 MB\nbundle = fetch_skill_bundle(url)  # 'skill bundle exceeds size limit'\n\n# after\nurl = \"https://github.com/acme/mono/tree/main/skills/pdf-tools\"  # small subfolder\nbundle = fetch_skill_bundle(url)","handlingStrategy":"validation","validationCode":"import httpx\n\nBUNDLE_LIMIT = 2_000_000\n\ndef bundle_size_ok(owner: str, repo: str, ref: str, path: str, token: str | None = None) -> bool:\n    \"\"\"Sum file sizes from the Git trees API; True if the folder stays under the cap.\"\"\"\n    h = {\"Accept\": \"application/vnd.github+json\"}\n    if token:\n        h[\"Authorization\"] = f\"Bearer {token}\"\n    r = httpx.get(f\"https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1\", headers=h, timeout=30.0)\n    prefix = path.strip(\"/\") + \"/\" if path else \"\"\n    total = sum(e.get(\"size\", 0) for e in r.json().get(\"tree\", [])\n                if e[\"type\"] == \"blob\" and e[\"path\"].startswith(prefix))\n    return total <= BUNDLE_LIMIT","typeGuard":null,"tryCatchPattern":"try:\n    files, src = fetch_skill_bundle(url)\nexcept SkillImportError as e:\n    if \"exceeds size limit\" in str(e):\n        report(f\"{url}: bundle over 2 MB — link the specific skill subfolder or externalize large assets\")\n        return None\n    raise","preventionTips":["Link the innermost folder containing SKILL.md, not a parent directory","Keep the whole skill folder under 2 MB; host big references externally","Budget ~30 KB average per file with the 64-file cap as a rule of thumb"],"tags":["size-limit","skill-import","github","quota"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}