odysseus-dev/odysseus · warning · SkillImportError
URL is required
Error message
URL is required
What it means
Raised at the top of parse_skill_source (services/memory/skill_importer.py) when the import URL, after strip(), is empty. It is the earliest, cheapest validation: no network or parsing happens before it, so hitting it means the caller passed '', None (str-coerced), or a whitespace-only string as the skill source URL.
Source
Thrown at services/memory/skill_importer.py:269
timeout=timeout,
) as client:
r = client.get(current, headers=headers)
if r.status_code in (301, 302, 303, 307, 308):
location = r.headers.get("location")
if not location:
return r
current = urljoin(str(r.url), location)
continue
return r
raise SkillImportError("too many redirects while fetching skill bundle")
def parse_skill_source(url: str) -> ResolvedSource:
"""Normalize skills.sh / GitHub web URLs into owner/repo/ref/path."""
url = (url or "").strip()
if not url:
raise SkillImportError("URL is required")
# ``urlparse`` only reports an unambiguous scheme when the URL carries the
# ``scheme://`` form. Opaque schemes (``mailto:``, ``javascript:``) and a
# schemeless ``host:port`` both parse a "scheme" that is not one, so they
# fall through to the host check below and are rejected on the host instead.
scheme = urlparse(url).scheme.lower()
if scheme not in ("http", "https"):
if scheme and url.lower().startswith(f"{scheme}://"):
raise SkillImportError(f"unsupported URL scheme: {scheme}")
# Schemeless "github.com/owner/repo" — accept only a supported host.
rough_host = (urlparse("//" + url).hostname or "").lower()
if rough_host not in _GITHUB_HOSTS and rough_host not in _SKILLS_SH_HOSTS:
raise SkillImportError("Only GitHub or skills.sh URLs are supported")
url = "https://" + url
parsed = urlparse(url)
hostname = (parsed.hostname or "").lower()
if hostname not in _GITHUB_HOSTS and hostname not in _SKILLS_SH_HOSTS:View on GitHub (pinned to f9235ebbf1)
Solutions
- Check the caller: log the exact url value right before parse_skill_source to confirm it is empty.
- Fix the upstream source of the URL (form field, env var, config key) so a real GitHub/skills.sh URL is supplied.
- Add a required-field check in the UI/handler so users get a friendly 'URL is required' before the request reaches the parser.
- If the value is None by design in some path, guard that path before calling import.
Example fix
# before
url = request.form.get("url", "") # unset field -> ""
parse_skill_source(url)
SkillImportError: URL is required
# after
url = (request.form.get("url") or "").strip()
if not url:
return bad_request("Please paste a GitHub or skills.sh skill URL")
parse_skill_source(url) Defensive patterns
Strategy: validation
Validate before calling
def validated_skill_url(raw: str | None) -> str:
url = (raw or '').strip()
if not url:
raise ValueError('skill URL is required')
return url Type guard
def has_skill_url(raw) -> bool:
return isinstance(raw, str) and bool(raw.strip()) Try / catch
from services.memory.skill_importer import SkillImportError, parse_skill_source
try:
src = parse_skill_source(url or '')
except SkillImportError as e:
if str(e) == 'URL is required':
return bad_request('Please paste a skill URL.')
raise Prevention
- Make the URL field required in the UI and disable submit while empty.
- strip() user input before every parse call.
- Distinguish empty-input errors from format errors in handlers so users get actionable messages.
When it happens
Trigger: Calling the skill-import API/handler with url omitted, an empty string, or a value that stringifies to blank; upstream UI passing an unset form field; a script reading a URL from an env var or clipboard that came back empty.
Common situations: Frontend bug submitting the import dialog before the URL field is filled; automation where the URL variable was never populated; copy-paste that grabbed only whitespace; None default leaking through from a calling layer.
Related errors
- d && d.detail ? d.detail : ('HTTP ' + res.status)
- result.detail || 'Failed to rename session'
- Invalid backup file: ' + e.message
- Import failed: server returned ${res.status}
- invalid JSON in {path}: {exc.msg} at line {exc.lineno}, colu
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/65d415aaa8d27be0.
Report an issue: GitHub.