odysseus-dev/odysseus · error · Error
data.detail || 'Auto-sort failed'
Error message
data.detail || 'Auto-sort failed'
What it means
Raised by _fetch_text when the bytes fetched by _fetch_bytes cannot be decoded as UTF-8. The importer only handles text files (every bundle entry maps relative path to decoded str), so a binary asset — image, PDF, zip, sqlite db — is rejected at decode time.
Source
Thrown at static/app.js:1427
_syncSortChecks();
// AI auto-sort — spinner on the sort button itself. Used by both
// the main "★ Tidy" button (AI) and the sub-row "Tidy" button
// (no AI, Phase 1 cleanup only) via the skipLlm flag.
async function _runTidy(skipLlm) {
const btnIcon = sortBtn.querySelector('.sort-icon');
if (btnIcon) btnIcon.style.display = 'none';
const wp = spinnerModule.create('', 'clean', 'whirlpool');
const wpEl = wp.createElement();
wpEl.style.cssText = 'width:13px;height:13px;display:inline-block;vertical-align:middle;margin-top:-5px;';
sortBtn.appendChild(wpEl);
wp.start();
sortDropdown.style.display = 'none';
try {
const url = `${API_BASE}/api/sessions/auto-sort${skipLlm ? '?skip_llm=true' : ''}`;
const res = await fetch(url, { method: 'POST' });
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Auto-sort failed');
if (data.status === 'ok') {
sessionModule.setSortMode(null); // clear sort — tidy creates manual folder order
_syncSortChecks();
if (skipLlm) {
// No-AI path: just report what got cleaned. No "unfiled
// remaining" prompt because we never tried to file anything.
const cleaned = (data.deleted_empty || 0) + (data.deleted_throwaway || 0);
uiModule.showToast(cleaned ? `Cleaned ${cleaned} empty/throwaway chat${cleaned === 1 ? '' : 's'}` : 'Already clean');
} else {
// Tidy now works in batches (15 most-recent unfiled per click)
// so the user gets fast feedback and a manageable LLM call
// even with hundreds of chats. Tell them what's left.
const remaining = data.unfiled_remaining || 0;
let msg;
if (data.updated > 0) {
msg = `Sorted ${data.updated} into ${data.folders.length} folder${data.folders.length === 1 ? '' : 's'}`;
if (remaining > 0) msg += ` — ${remaining} unfiled left, hit Group again`;
} else if (remaining > 0) {View on GitHub (pinned to f9235ebbf1)
Solutions
- Remove binary assets from the skill folder — reference images/docs by absolute URL inside SKILL.md instead
- If the file is meant to be text, re-save it as UTF-8 (watch for UTF-16 exports from Windows editors or BOM issues)
- Rename mislabeled files so the real extension matches the real content, keeping binary extensions out of the bundle
- Ensure you linked a SKILL.md file or a folder containing SKILL.md, not an asset file
Example fix
# before my-skill/ SKILL.md architecture.png # binary -> 'non-text file' during traversal # after my-skill/ SKILL.md # 
Defensive patterns
Strategy: validation
Validate before calling
_TEXT_EXT = {".md", ".txt", ".json", ".yaml", ".yml", ".toml", ".py", ".js", ".ts", ".sh", ""}
def is_probably_text_asset(name: str) -> bool:
return Path(name).suffix.lower() in _TEXT_EXT
def decodes_as_utf8(data: bytes) -> bool:
try:
data.decode("utf-8")
return True
except UnicodeDecodeError:
return False Try / catch
try:
files, src = fetch_skill_bundle(url)
except SkillImportError as e:
if "non-text file" in str(e):
report(f"{url}: binary asset in bundle — remove it or reference it by URL in SKILL.md")
return None
raise Prevention
- Never commit binaries (png/pdf/zip/db) inside a skill folder; reference them by absolute URL
- Save text files as UTF-8, not UTF-16/Latin-1
- Lint skill repos for binary signatures in *.md/*.txt files
When it happens
Trigger: A direct /blob/... URL pointing at a binary file (e.g. logo.png), or a directory traversal where _is_text_file(name) matched the extension but the content is binary (a .md/.txt file that is actually binary, or a mislabeled extension). _fetch_bytes succeeds, then data.decode('utf-8') raises UnicodeDecodeError which is chained into this SkillImportError.
Common situations: Skill authors commit screenshots, PDFs, or fonts alongside SKILL.md; users link directly to a binary in the repo. Also a text file saved in UTF-16/Latin-1 with a .md extension fails strict UTF-8 decoding.
Related errors
- d && d.detail ? d.detail : ('HTTP ' + res.status)
- Server did not return a session id
- result.detail || 'Failed to rename session'
- d.detail || 'Failed'
- Invalid backup file: ' + e.message
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/d2e036f8d1577584.
Report an issue: GitHub.