iflytek/astron-agent · error · RuntimeError

Artifact upload configuration is unavailable

Error message

Artifact upload configuration is unavailable

What it means

_load_artifact_upload_url validates the SKILL_SANDBOX_ARTIFACT_UPLOAD_URL env var with a strict allowlist: http/https scheme, a hostname, no userinfo, path exactly '/workflow/artifacts/internal-upload', no query or fragment, length <= 2048, no CR/LF/TAB, and a parseable port. Any deviation raises ValueError which is converted to RuntimeError('Artifact upload configuration is unavailable'). It surfaces from upload() and also from is_configured().

Solutions

  1. Set SKILL_SANDBOX_ARTIFACT_UPLOAD_URL to the exact full URL ending in /workflow/artifacts/internal-upload, e.g. https://console.example.com/workflow/artifacts/internal-upload
  2. Remove any query string, fragment, trailing slash differences, or embedded user:password from the URL
  3. Verify the URL length is <= 2048 and the port (if any) is numeric/valid
  4. Confirm with the console backend team that the internal-upload endpoint path matches the deployed version

Example fix

// before
SKILL_SANDBOX_ARTIFACT_UPLOAD_URL=https://console.example.com

// after
SKILL_SANDBOX_ARTIFACT_UPLOAD_URL=https://console.example.com/workflow/artifacts/internal-upload
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit

ARTIFACT_PATH = "/workflow/artifacts/internal-upload"

def artifact_upload_url_ok() -> bool:
    url = (os.getenv("SKILL_SANDBOX_ARTIFACT_UPLOAD_URL") or "").strip()
    if not url or len(url) > 2048 or any(c in url for c in "\r\n\t"):
        return False
    p = urlsplit(url)
    try:
        p.port
    except ValueError:
        return False
    return (
        p.scheme in ("http", "https")
        and bool(p.hostname)
        and p.username is None
        and p.password is None
        and p.path == ARTIFACT_PATH
        and not p.query
        and not p.fragment
    )

Type guard

from urllib.parse import urlsplit

def is_valid_artifact_upload_url(url: object) -> bool:
    if not isinstance(url, str) or not url:
        return False
    p = urlsplit(url)
    return p.scheme in ("http", "https") and bool(p.hostname) and p.path == "/workflow/artifacts/internal-upload"

Try / catch

try:
    configured = sandbox.is_configured()
except RuntimeError as exc:
    if "Artifact upload configuration" in str(exc):
        raise ConfigError("SKILL_SANDBOX_ARTIFACT_UPLOAD_URL must be full URL ending in /workflow/artifacts/internal-upload") from exc
    raise

Prevention

When it happens

Trigger: upload() or is_configured() called when SKILL_SANDBOX_ARTIFACT_UPLOAD_URL is empty, or set to a URL whose path is not exactly /workflow/artifacts/internal-upload, whose scheme is not http/https, that embeds credentials, a query string, a fragment, control characters, exceeds 2048 chars, or has an invalid port (ValueError at parsed.port) — converted at line 193.

Common situations: Operator configured the base service URL (e.g. https://console.example.com) instead of the full internal upload path; a trailing slash or query param added for debugging; URL-embedded basic-auth credentials; missing env var because the artifact-upload side of the sandbox was never deployed.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/565e36cb86a7d0e5. Report an issue: GitHub.

Appendix: source

Thrown at core/agent/service/plugin/skill_sandbox.py:193

    artifact_upload_url = (os.getenv(ARTIFACT_UPLOAD_URL_ENV) or "").strip()
    try:
        parsed = urlsplit(artifact_upload_url)
        if (
            not artifact_upload_url
            or len(artifact_upload_url) > 2048
            or any(char in artifact_upload_url for char in ("\r", "\n", "\t"))
            or parsed.scheme not in {"http", "https"}
            or not parsed.hostname
            or parsed.username is not None
            or parsed.password is not None
            or parsed.path != ARTIFACT_UPLOAD_PATH
            or bool(parsed.query)
            or bool(parsed.fragment)
        ):
            raise ValueError
        parsed.port
    except (TypeError, ValueError):
        raise RuntimeError(ARTIFACT_UPLOAD_CONFIG_ERROR) from None
    return artifact_upload_url


def _load_runtime_config_url() -> str:
    runtime_config_url = (os.getenv(RUNTIME_CONFIG_URL_ENV) or "").strip()
    try:
        parsed = urlsplit(runtime_config_url)
        if (
            not runtime_config_url
            or len(runtime_config_url) > 2048
            or any(char in runtime_config_url for char in ("\r", "\n", "\t"))
            or parsed.scheme not in {"http", "https"}
            or not parsed.hostname
            or parsed.username is not None
            or parsed.password is not None
            or parsed.path != RUNTIME_CONFIG_PATH
            or bool(parsed.query)
            or bool(parsed.fragment)

View on GitHub (pinned to 5e758547a8)