{"record":{"id":"a96ab812fd480aeb","repo":"iflytek/astron-agent","slug":"artifact-upload-credential-is-missing-or-invalid","errorCode":null,"errorMessage":"Artifact upload credential is missing or invalid","messagePattern":"Artifact upload credential is missing or invalid","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"core/agent/service/plugin/skill_sandbox.py","lineNumber":168,"sourceCode":"        pass\n    print('{\"error\":\"snapshot_failed\"}')\nfinally:\n    if source_fd is not None:\n        os.close(source_fd)\n    if destination_fd is not None:\n        os.close(destination_fd)\n\"\"\".strip()\n\n\ndef _load_artifact_upload_token() -> str:\n    token = (os.getenv(ARTIFACT_UPLOAD_TOKEN_ENV) or \"\").strip()\n    if not token:\n        token_file = (os.getenv(ARTIFACT_UPLOAD_TOKEN_FILE_ENV) or \"\").strip()\n        if token_file:\n            try:\n                token = Path(token_file).read_text(encoding=\"utf-8\").strip()\n            except (OSError, UnicodeError) as exc:\n                raise RuntimeError(ARTIFACT_UPLOAD_CREDENTIAL_ERROR) from exc\n    if len(token) < MIN_ARTIFACT_UPLOAD_TOKEN_LENGTH or \"\\r\" in token or \"\\n\" in token:\n        raise RuntimeError(ARTIFACT_UPLOAD_CREDENTIAL_ERROR)\n    return token\n\n\ndef _load_artifact_upload_url() -> str:\n    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","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/agent/service/plugin/skill_sandbox.py#L150-L186","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN to a token of at least 32 characters with no whitespace/newlines","Or set SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE to a readable file containing only the token (single line, UTF-8)","Check the token file is mounted/accessible inside the container (ls/cat it in the pod) and has correct permissions","Regenerate/reissue the credential if it is shorter than 32 characters or contains embedded newlines"],"exampleFix":"// before\n# env has no SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN\n\n// after\nexport SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN=\"$(openssl rand -hex 32)\"\n# or\nexport SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE=/etc/secrets/artifact_upload_token","handlingStrategy":"validation","validationCode":"import os\nfrom pathlib import Path\n\ndef artifact_upload_credential_ready() -> bool:\n    token = (os.getenv(\"SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN\") or \"\").strip()\n    if not token:\n        tf = (os.getenv(\"SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN_FILE\") or \"\").strip()\n        if not tf:\n            return False\n        try:\n            token = Path(tf).read_text(encoding=\"utf-8\").strip()\n        except (OSError, UnicodeError):\n            return False\n    return len(token) >= 32 and \"\\r\" not in token and \"\\n\" not in token","typeGuard":"def is_valid_artifact_token(token: object) -> bool:\n    return (\n        isinstance(token, str)\n        and len(token) >= 32\n        and \"\\r\" not in token\n        and \"\\n\" not in token\n    )","tryCatchPattern":"try:\n    await sandbox.upload(...)\nexcept RuntimeError as exc:\n    if \"Artifact upload credential\" in str(exc):\n        raise ConfigError(\n            \"SKILL_SANDBOX_ARTIFACT_UPLOAD_TOKEN (or *_FILE) missing/invalid; \"\n            \"need >=32 chars, no newlines\"\n        ) from exc\n    raise","preventionTips":["Provision the credential via a mounted secret file in K8s rather than a bare env var","Assert required SKILL_SANDBOX_* env vars at service startup with a fail-fast config check","Generate tokens with `openssl rand -hex 32` (64 chars, no whitespace)","Check for None/empty token in deployment smoke tests before enabling artifact upload"],"tags":["configuration","authentication","env-var","skill-sandbox"],"backgroundTag":"missing-credentials","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"}