odysseus-dev/odysseus · error · Error
d.detail || 'Failed'
Error message
d.detail || 'Failed'
What it means
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).
Source
Thrown at static/js/admin.js:2580
};
input.addEventListener('blur', commit);
input.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); input.blur(); } });
});
// Scope toggle change → PATCH the whole scopes array for this token.
list.querySelectorAll('.adm-tok-scope').forEach(cb => {
cb.addEventListener('change', async () => {
const tokenId = cb.dataset.tokenId;
const panel = list.querySelector(`[data-adm-tok-perm="${tokenId}"]`);
const msg = list.querySelector(`.adm-tok-scope-msg[data-token-id="${tokenId}"]`);
const scopes = Array.from(panel.querySelectorAll('.adm-tok-scope:checked')).map(input => input.dataset.scope);
try {
const r = await fetch(`/api/tokens/${tokenId}`, {
method: 'PATCH', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ scopes }),
});
const d = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(d.detail || 'Failed');
if (msg) { msg.textContent = 'Saved'; msg.style.color = 'var(--green, #50fa7b)'; setTimeout(() => { msg.textContent = ''; }, 1200); }
} catch (err) {
cb.checked = !cb.checked;
if (msg) { msg.textContent = (err && err.message) || 'Failed'; msg.style.color = 'var(--red)'; }
}
});
});
} catch (e) { list.innerHTML = '<div class="admin-error">Failed to load tokens</div>'; }
}
function initTokenForm() {
const addBtn = el('adm-tokenAddBtn');
if (!addBtn || addBtn.dataset.bound) return;
addBtn.dataset.bound = '1';
addBtn.addEventListener('click', async () => {
const msg = el('adm-tokenMsg');
const reveal = el('adm-tokenReveal');
msg.textContent = ''; msg.className = ''; reveal.style.display = 'none';View on GitHub (pinned to f9235ebbf1)
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)
Example fix
# before url = "https://github.com/acme/mono/tree/main" # walks whole repo, >2 MB bundle = fetch_skill_bundle(url) # 'skill bundle exceeds size limit' # after url = "https://github.com/acme/mono/tree/main/skills/pdf-tools" # small subfolder bundle = fetch_skill_bundle(url)
Defensive patterns
Strategy: validation
Validate before calling
import httpx
BUNDLE_LIMIT = 2_000_000
def bundle_size_ok(owner: str, repo: str, ref: str, path: str, token: str | None = None) -> bool:
"""Sum file sizes from the Git trees API; True if the folder stays under the cap."""
h = {"Accept": "application/vnd.github+json"}
if token:
h["Authorization"] = f"Bearer {token}"
r = httpx.get(f"https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1", headers=h, timeout=30.0)
prefix = path.strip("/") + "/" if path else ""
total = sum(e.get("size", 0) for e in r.json().get("tree", [])
if e["type"] == "blob" and e["path"].startswith(prefix))
return total <= BUNDLE_LIMIT Try / catch
try:
files, src = fetch_skill_bundle(url)
except SkillImportError as e:
if "exceeds size limit" in str(e):
report(f"{url}: bundle over 2 MB — link the specific skill subfolder or externalize large assets")
return None
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Server did not return a session id
- d && d.detail ? d.detail : ('HTTP ' + res.status)
- {context} must stay on GitHub (got {host or 'unknown host'})
- skills.sh did not redirect to GitHub — open the skill's repo
- Invalid raw GitHub URL
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/ed123dfce96ca9e0.
Report an issue: GitHub.