odysseus-dev/odysseus · error · Error

Import failed: server returned ${res.status}

Error message

Import failed: server returned ${res.status}

What it means

Raised by Skills.install_bundle when the files dict passed in is empty (falsy). This is the earliest guard before pick_skill_md/from_markdown run — an empty bundle means nothing to install. Like error 986 it only fires on direct calls: fetch_skill_bundle either returns non-empty files or raises error 985 first.

Source

Thrown at static/js/admin.js:2831

    const msg = el('adm-backupMsg');
    const btn = el('adm-importDataBtn');
    btn.disabled = true; btn.textContent = 'Importing...'; msg.textContent = '';
    try {
      const text = (await file.text()).replace(/^\uFEFF/, '').trim();
      let data;
      try {
        data = JSON.parse(text);
      } catch (e) {
        throw new Error('Invalid backup file: ' + e.message);
      }
      const res = await fetch('/api/import', {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      const result = await res.json().catch(() => null);
      if (!result) {
        throw new Error(`Import failed: server returned ${res.status}`);
      }
      if (res.ok && result.ok) {
        msg.textContent = result.message || 'Import successful.'; msg.className = 'admin-success';
      } else {
        msg.textContent = result.message || result.detail || 'Import failed'; msg.className = 'admin-error';
      }
    } catch (e) { msg.textContent = 'Import failed: ' + e.message; msg.className = 'admin-error'; }
    btn.disabled = false; btn.textContent = 'Import Data';
  });
}

/* ── Danger Zone ── */
function initDangerZone() {
  // Per-category Danger Zone wipes. Each button declares its target
  // via data-wipe-kind; one delegated handler handles double-confirm,
  // POSTs to /api/admin/wipe/{kind}, and writes the result.
  const _LABELS = {
    chats: 'chats', memory: 'memory entries', skills: 'skills',

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Populate the bundle before installing: fetch via fetch_skill_bundle, or verify your local traversal actually collected files
  2. Check intermediate filters (extension allowlists, size checks) — they may be excluding everything
  3. Fail earlier with a clearer message at the collection site rather than passing an empty dict into install_bundle
  4. Log the dict size/keys just before calling install_bundle to confirm what the traversal produced

Example fix

# before
files = {p.read_text('utf-8') for p in dir.glob('*.x') if keep(p)}  # keep() rejects all
skills.install_bundle(files)  # 'empty bundle'

# after
files = {p.name: p.read_text('utf-8') for p in dir.glob('SKILL.md')}
if not files:
    raise ValueError(f"no SKILL.md collected from {dir}")
skills.install_bundle(files)
Defensive patterns

Strategy: validation

Validate before calling

def is_installable_bundle(files: dict[str, str]) -> bool:
    return bool(files) and any(p.lower().endswith("skill.md") for p in files)

Type guard

from typing import TypeGuard

def is_non_empty_bundle(files: dict[str, str]) -> TypeGuard[dict[str, str]]:
    return len(files) > 0

Try / catch

try:
    skills.install_bundle(files)
except SkillImportError as e:
    if str(e) == "empty bundle":
        raise ValueError("collected nothing to install — check source path and filters") from e
    raise

Prevention

When it happens

Trigger: Calling install_bundle({}) or install_bundle(files) where files was populated by a loop/traversal that matched nothing (empty directory walk, filtered-out extensions, failed downloads swallowed into an empty map).

Common situations: Custom import pipelines that glob a local directory which is empty or whose files were all filtered out; race where the source directory was deleted between listing and install; passing fetch output after a failed fetch that returned {} instead of raising.

Related errors


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