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
- 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
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
- 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
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
- Artifact upload credential is missing or invalid
- Sandbox runtime configuration is unavailable
- 40028
- Invalid PRIVATE_ENDPOINT_ALLOW_LIST entry
- PRIVATE_ENDPOINT_ALLOW_LIST entries must not include params…
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)