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

  1. 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)
  2. Or use the plain repo root https://github.com/<owner>/<repo> — this parses with ref='main' and path=''
  3. If linking a specific commit or tag, wrap it in tree/ form: .../tree/<tag-or-sha>/<path>
  4. 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

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


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