odysseus-dev/odysseus · error · Error
Invalid backup file: ' + e.message
Error message
Invalid backup file: ' + e.message
What it means
Raised by pick_skill_md when no key in the files dict ends (case-insensitively) in 'skill.md'. In the normal flow fetch_skill_bundle already guarantees a SKILL.md exists (error 985), so this fires only when pick_skill_md or install_bundle is called directly with a caller-constructed file map that lacks one.
Source
Thrown at static/js/admin.js:2822
} catch (e) { msg.textContent = 'Export failed: ' + e.message; msg.className = 'admin-error'; }
btn.disabled = false; btn.textContent = 'Export Data';
});
const fileInput = el('adm-importFile');
el('adm-importDataBtn').addEventListener('click', () => { fileInput.value = ''; fileInput.click(); });
fileInput.addEventListener('change', async () => {
const file = fileInput.files[0];
if (!file) return;
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';
});View on GitHub (pinned to f9235ebbf1)
Solutions
- Ensure the files dict passed to install_bundle/pick_skill_md contains at least one key whose path ends in 'skill.md' (any directory prefix is fine, e.g. 'my-skill/SKILL.md')
- Prefer using fetch_skill_bundle's output unmodified — it validates this invariant before returning
- If building bundles locally, copy your skill manifest to '<name>/SKILL.md' in the dict before calling install
- Check for key-mangling middleware (path normalization, lowercasing that renames the file) between fetch and install
Example fix
# before
files = {"guide.md": md_text, "prompt.txt": prompt}
skills.install_bundle(files) # 'bundle has no SKILL.md'
# after
files = {"my-skill/SKILL.md": md_text, "my-skill/prompt.txt": prompt}
skills.install_bundle(files) Defensive patterns
Strategy: type-guard
Validate before calling
def bundle_has_skill_md(files: dict[str, str]) -> bool:
return any(p.lower().endswith("skill.md") for p in files) Type guard
from typing import TypeGuard
def is_valid_bundle(files: dict[str, str]) -> TypeGuard[dict[str, str]]:
"""Narrows to a bundle install_bundle can process."""
return bool(files) and any(p.lower().endswith("skill.md") for p in files) Try / catch
from services.memory.skill_importer import SkillImportError
if not is_valid_bundle(files):
raise ValueError("bundle must contain a path ending in skill.md")
try:
skills.install_bundle(files)
except SkillImportError as e:
if "no SKILL.md" in str(e):
fix_bundle_naming(files) # e.g. rename primary doc to '<name>/SKILL.md'
else:
raise Prevention
- Prefer passing fetch_skill_bundle output straight to install_bundle without key rewrites
- Add is_valid_bundle as an assert/CI check in custom bundle builders
- Keep the SKILL.md entry's path suffix intact through any normalization
When it happens
Trigger: Calling skills install_bundle(files) / pick_skill_md(files) with a hand-built dict like {'README.md': '...', 'prompt.txt': '...'}; or a caller that filtered/renamed fetch_skill_bundle's output and dropped the skill.md entry before installing.
Common situations: Programmatic integrations that assemble bundles from local directories or templates and forget the naming contract (path must end in 'skill.md'); post-processing steps that rewrite keys (e.g. stripping directories) and break the suffix.
Related errors
- d && d.detail ? d.detail : ('HTTP ' + res.status)
- result.detail || 'Failed to rename session'
- Import failed: server returned ${res.status}
- unsafe path: {rel!r}
- URL is required
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/9fa9b63dc6e54f30.
Report an issue: GitHub.