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

  1. Slim the skill folder: keep SKILL.md plus only essential assets under 2 MB total, link everything else by external URL
  2. Link the exact subfolder containing SKILL.md instead of a parent folder, so traversal does not pull in sibling content
  3. Move large reference material to a docs/ folder outside the skill path or host it separately
  4. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/ed123dfce96ca9e0. Report an issue: GitHub.