{"record":{"id":"565e36cb86a7d0e5","repo":"iflytek/astron-agent","slug":"artifact-upload-configuration-is-unavailable","errorCode":null,"errorMessage":"Artifact upload configuration is unavailable","messagePattern":"Artifact upload configuration is unavailable","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"core/agent/service/plugin/skill_sandbox.py","lineNumber":193,"sourceCode":"    artifact_upload_url = (os.getenv(ARTIFACT_UPLOAD_URL_ENV) or \"\").strip()\n    try:\n        parsed = urlsplit(artifact_upload_url)\n        if (\n            not artifact_upload_url\n            or len(artifact_upload_url) > 2048\n            or any(char in artifact_upload_url for char in (\"\\r\", \"\\n\", \"\\t\"))\n            or parsed.scheme not in {\"http\", \"https\"}\n            or not parsed.hostname\n            or parsed.username is not None\n            or parsed.password is not None\n            or parsed.path != ARTIFACT_UPLOAD_PATH\n            or bool(parsed.query)\n            or bool(parsed.fragment)\n        ):\n            raise ValueError\n        parsed.port\n    except (TypeError, ValueError):\n        raise RuntimeError(ARTIFACT_UPLOAD_CONFIG_ERROR) from None\n    return artifact_upload_url\n\n\ndef _load_runtime_config_url() -> str:\n    runtime_config_url = (os.getenv(RUNTIME_CONFIG_URL_ENV) or \"\").strip()\n    try:\n        parsed = urlsplit(runtime_config_url)\n        if (\n            not runtime_config_url\n            or len(runtime_config_url) > 2048\n            or any(char in runtime_config_url for char in (\"\\r\", \"\\n\", \"\\t\"))\n            or parsed.scheme not in {\"http\", \"https\"}\n            or not parsed.hostname\n            or parsed.username is not None\n            or parsed.password is not None\n            or parsed.path != RUNTIME_CONFIG_PATH\n            or bool(parsed.query)\n            or bool(parsed.fragment)","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/agent/service/plugin/skill_sandbox.py#L175-L211","documentation":"_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().","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Remove any query string, fragment, trailing slash differences, or embedded user:password from the URL","Verify the URL length is <= 2048 and the port (if any) is numeric/valid","Confirm with the console backend team that the internal-upload endpoint path matches the deployed version"],"exampleFix":"// before\nSKILL_SANDBOX_ARTIFACT_UPLOAD_URL=https://console.example.com\n\n// after\nSKILL_SANDBOX_ARTIFACT_UPLOAD_URL=https://console.example.com/workflow/artifacts/internal-upload","handlingStrategy":"validation","validationCode":"from urllib.parse import urlsplit\n\nARTIFACT_PATH = \"/workflow/artifacts/internal-upload\"\n\ndef artifact_upload_url_ok() -> bool:\n    url = (os.getenv(\"SKILL_SANDBOX_ARTIFACT_UPLOAD_URL\") or \"\").strip()\n    if not url or len(url) > 2048 or any(c in url for c in \"\\r\\n\\t\"):\n        return False\n    p = urlsplit(url)\n    try:\n        p.port\n    except ValueError:\n        return False\n    return (\n        p.scheme in (\"http\", \"https\")\n        and bool(p.hostname)\n        and p.username is None\n        and p.password is None\n        and p.path == ARTIFACT_PATH\n        and not p.query\n        and not p.fragment\n    )","typeGuard":"from urllib.parse import urlsplit\n\ndef is_valid_artifact_upload_url(url: object) -> bool:\n    if not isinstance(url, str) or not url:\n        return False\n    p = urlsplit(url)\n    return p.scheme in (\"http\", \"https\") and bool(p.hostname) and p.path == \"/workflow/artifacts/internal-upload\"","tryCatchPattern":"try:\n    configured = sandbox.is_configured()\nexcept RuntimeError as exc:\n    if \"Artifact upload configuration\" in str(exc):\n        raise ConfigError(\"SKILL_SANDBOX_ARTIFACT_UPLOAD_URL must be full URL ending in /workflow/artifacts/internal-upload\") from exc\n    raise","preventionTips":["Configure the complete URL including the fixed internal-upload path, not just the host","Add a startup config validator that runs the same urlsplit checks before the service accepts traffic","Document the exact expected value in deployment templates/helm values","Keep query parameters out of internal service URLs; use env vars for options instead"],"tags":["configuration","env-var","url-validation","skill-sandbox"],"backgroundTag":"invalid-url","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}