odysseus-dev/odysseus · error · Error
d && d.detail ? d.detail : ('HTTP ' + res.status)
Error message
d && d.detail ? d.detail : ('HTTP ' + res.status) What it means
Raised by parse_skill_source when a github.com URL path has 3 segments (owner/repo/<word>) or its third segment is not 'tree' or 'blob'. The parser only accepts repo-root URLs (2 segments), or full /tree/<ref>/... or /blob/<ref>/... URLs; anything else (e.g. /releases/tag/v1, /raw/..., /commits/main) falls into the else branch. ref defaults to 'main', so a bare repo URL assumes the default branch is main.
Source
Thrown at src/visual_report.py:1090
}}, {{ rootMargin: '-10% 0px -75% 0px', threshold: 0 }});
headings.forEach(function(h) {{ io.observe(h); }});
}}
// Chat about this research — POST to spinoff and redirect to the new chat
var chatBtn = document.getElementById('btn-chat-about');
if (chatBtn) {{
chatBtn.addEventListener('click', function() {{
var researchId = chatBtn.dataset.researchId;
if (!researchId) return;
var origLabel = chatBtn.innerHTML;
chatBtn.disabled = true;
chatBtn.innerHTML = '<span>Creating chat…</span>';
fetch('/api/research/spinoff/' + encodeURIComponent(researchId), {{
method: 'POST', credentials: 'same-origin',
}}).then(function(res) {{
if (!res.ok) {{
return res.json().then(function(d) {{
throw new Error(d && d.detail ? d.detail : ('HTTP ' + res.status));
}}, function() {{ throw new Error('HTTP ' + res.status); }});
}}
return res.json();
}}).then(function(data) {{
if (!data || !data.session_id) {{
throw new Error('Server did not return a session id');
}}
var url = '/#' + data.session_id;
var opened = false;
// The report typically opens in a new tab — if we have access to the
// original Odysseus tab, navigate it and close this report tab so the
// user lands directly in the new chat.
try {{
if (window.opener && !window.opener.closed) {{
window.opener.location.href = url;
window.opener.location.reload();
window.opener.focus();
opened = true;View on GitHub (pinned to f9235ebbf1)
Solutions
- Use a URL of the form https://github.com/<owner>/<repo>/tree/<branch>/<path> (folder) or https://github.com/<owner>/<repo>/blob/<branch>/<path> (SKILL.md file)
- Or use the plain repo root https://github.com/<owner>/<repo> — this parses with ref='main' and path=''
- If linking a specific commit or tag, wrap it in tree/ form: .../tree/<tag-or-sha>/<path>
- Verify the branch name in the URL actually exists (master vs main only matters later, at fetch time, since bare URLs default to main)
Example fix
// before url = "https://github.com/acme/awesome-skills/releases/tag/v2" bundle = fetch_skill_bundle(url) # SkillImportError // after url = "https://github.com/acme/awesome-skills/tree/v2/skills/debug-helper" bundle = fetch_skill_bundle(url)
Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlparse
_RE = re.compile(r"^https?://(?:www\.)?github\.com/[^/]+/[^/]+(?:/(?:tree|blob)/[^/]+(?:/.+)?)?/?$")
def is_valid_skill_github_url(url: str) -> bool:
return bool(_RE.match(url.strip())) Type guard
def is_parseable_skill_source(url: str) -> bool:
"""True when parse_skill_source will not raise on URL shape."""
try:
bits = [p for p in urlparse(url).path.split("/") if p]
except Exception:
return False
return len(bits) == 2 or (len(bits) >= 4 and bits[2] in ("tree", "blob")) Try / catch
from services.memory.skill_importer import SkillImportError, fetch_skill_bundle
try:
files, src = fetch_skill_bundle(url)
except SkillImportError as e:
if "must include /tree" in str(e):
raise UserInputError(f"Re-link {url!r} as .../tree/<branch>/<path> or .../blob/<branch>/<path>") from e
raise Prevention
- In UI, build GitHub links from the address bar while viewing the folder or SKILL.md file, never from releases/commits pages
- Normalize user-pasted URLs through is_parseable_skill_source before calling the importer
- Offer a URL picker that only emits tree/ or blob/ forms
When it happens
Trigger: Calling fetch_skill_bundle()/parse_skill_source with 'https://github.com/owner/repo/releases/tag/v1.0' (bits[2]='releases'), 'https://github.com/owner/repo/blob' (len(bits)==3, no ref segment), or 'https://github.com/owner/repo/commit/abc123'. Only len(bits)==2, or len(bits)>=4 with bits[2] in ('tree','blob'), parse successfully.
Common situations: User pastes a link copied from the GitHub releases page, a commits page, or a compare URL instead of navigating to the folder/file view. Also happens when someone hand-trims a URL and drops the branch segment, leaving /blob with nothing after it.
Related errors
- result.detail || 'Failed to rename session'
- Server did not return a session id
- d.detail || 'Failed'
- Invalid backup file: ' + e.message
- Import failed: server returned ${res.status}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/3ce01417a07e9276.
Report an issue: GitHub.