iflytek/astron-agent · error · RuntimeError

Artifact upload credential is missing or invalid

Error message

Artifact upload credential is missing or invalid

What it means

The skill sandbox artifact-upload feature requires an internal upload credential at least MIN_ARTIFACT_UPLOAD_TOKEN_LENGTH (32) chars long with no CR/LF. _load_artifact_upload_token reads it from SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN, or from the file named by SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE. If neither is set, the token file is unreadable, or the resulting token is too short or contains newline characters, upload() raises this RuntimeError.

Solutions

  1. Set SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN to a token of at least 32 characters with no whitespace/newlines
  2. Or set SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE to a readable file containing only the token (single line, UTF-8)
  3. Check the token file is mounted/accessible inside the container (ls/cat it in the pod) and has correct permissions
  4. Regenerate/reissue the credential if it is shorter than 32 characters or contains embedded newlines

Example fix

// before
# env has no SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN

// after
export SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN="$(openssl rand -hex 32)"
# or
export SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE=/etc/secrets/artifact_upload_token
Defensive patterns

Strategy: validation

Validate before calling

import os
from pathlib import Path

def artifact_upload_credential_ready() -> bool:
    token = (os.getenv("SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN") or "").strip()
    if not token:
        tf = (os.getenv("SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE") or "").strip()
        if not tf:
            return False
        try:
            token = Path(tf).read_text(encoding="utf-8").strip()
        except (OSError, UnicodeError):
            return False
    return len(token) >= 32 and "\r" not in token and "\n" not in token

Type guard

def is_valid_artifact_token(token: object) -> bool:
    return (
        isinstance(token, str)
        and len(token) >= 32
        and "\r" not in token
        and "\n" not in token
    )

Try / catch

try:
    await sandbox.upload(...)
except RuntimeError as exc:
    if "Artifact upload credential" in str(exc):
        raise ConfigError(
            "SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN (or *_FILE) missing/invalid; "
            "need >=32 chars, no newlines"
        ) from exc
    raise

Prevention

When it happens

Trigger: upload() called when SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN is unset/empty and SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE is unset; Path.read_text on the token file raises OSError (missing file, permissions) or UnicodeError (invalid UTF-8) — line 168; or token length < 32 or contains \r/\n — line 169-170.

Common situations: Deployment env vars missing because the sandbox feature was never configured by an admin; the token file path points to a Kubernetes secret not mounted in the agent pod; a truncated or copy-pasted token shorter than 32 chars; a token file saved with a trailing newline inside quotes or Windows CRLF content.

Related errors


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

Appendix: source

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

        pass
    print('{"error":"snapshot_failed"}')
finally:
    if source_fd is not None:
        os.close(source_fd)
    if destination_fd is not None:
        os.close(destination_fd)
""".strip()


def _load_artifact_upload_token() -> str:
    token = (os.getenv(ARTIFACT_UPLOAD_TOKEN_ENV) or "").strip()
    if not token:
        token_file = (os.getenv(ARTIFACT_UPLOAD_TOKEN_FILE_ENV) or "").strip()
        if token_file:
            try:
                token = Path(token_file).read_text(encoding="utf-8").strip()
            except (OSError, UnicodeError) as exc:
                raise RuntimeError(ARTIFACT_UPLOAD_CREDENTIAL_ERROR) from exc
    if len(token) < MIN_ARTIFACT_UPLOAD_TOKEN_LENGTH or "\r" in token or "\n" in token:
        raise RuntimeError(ARTIFACT_UPLOAD_CREDENTIAL_ERROR)
    return token


def _load_artifact_upload_url() -> str:
    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

View on GitHub (pinned to 5e758547a8)