{"record":{"id":"554141814f47a474","repo":"infiniflow/ragflow","slug":"unsupported-artifact-type-relative-path-554141","errorCode":null,"errorMessage":"Unsupported artifact type: {relative_path}","messagePattern":"Unsupported artifact type: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agent/sandbox/providers/tenki.py","lineNumber":480,"sourceCode":"\n            # Reject symlinks. `is_symlink` is not populated by every SDK\n            # release, so also inspect the stat mode bits as the reliable check.\n            if getattr(entry, \"is_symlink\", False) or stat.S_ISLNK(entry.mode or 0):\n                raise RuntimeError(f\"Artifact symlinks are not allowed: {relative_path}\")\n            if entry.is_dir:\n                self._collect_artifacts_recursive(sandbox, remote_path, relative_path, artifacts, depth + 1)\n                continue\n\n            if len(artifacts) >= self.max_artifacts:\n                raise RuntimeError(f\"Tenki execution produced more than {self.max_artifacts} artifacts.\")\n\n            size = int(entry.size or 0)\n            if size > self.max_artifact_bytes:\n                raise RuntimeError(f\"Artifact exceeds {self.max_artifact_bytes} bytes: {relative_path}\")\n\n            ext = os.path.splitext(name)[1].lower()\n            if ext not in ALLOWED_ARTIFACT_EXTENSIONS:\n                raise RuntimeError(f\"Unsupported artifact type: {relative_path}\")\n\n            content = sandbox.fs.read_bytes(remote_path)\n            artifacts.append(\n                {\n                    \"name\": relative_path,\n                    \"content_b64\": base64.b64encode(content).decode(\"ascii\"),\n                    \"mime_type\": mimetypes.guess_type(name)[0] or \"application/octet-stream\",\n                    \"size\": size,\n                }\n            )\n\n    def _safe_terminate(self, sandbox) -> None:\n        # Best-effort: max_lifetime reclaims the sandbox if this fails.\n        try:\n            sandbox.terminate()\n        except Exception as exc:\n            logger.warning(\"Failed to terminate Tenki sandbox, relying on max_lifetime: %s\", exc)\n","sourceCodeStart":462,"sourceCodeEnd":498,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/agent/sandbox/providers/tenki.py#L462-L498","documentation":"Raised during artifact collection when an artifact file's extension is not in ALLOWED_ARTIFACT_EXTENSIONS ({.csv, .html, .jpeg, .jpg, .json, .pdf, .png, .svg}). The allowlist constrains what untrusted code can push back into the agent context; any other file type aborts collection.","triggerScenarios":"Script writes files like result.txt, model.pkl, data.parquet, or backup~ into artifacts/; the extension check (case-insensitive via lower()) rejects them before read_bytes().","commonSituations":"Generated code saving logs, pickles, or arbitrary working files into the artifacts dir by convention; tooling that assumes artifacts/ is a general output bucket.","solutions":["Have the script write only allowlisted types into artifacts/ and keep other files elsewhere in the work dir.","Convert outputs: serialize objects as .json, tabular data as .csv, render plots as .png/.svg.","If a type is genuinely required, extend ALLOWED_ARTIFACT_EXTENSIONS after reviewing the security implications for untrusted code."],"exampleFix":"# before\n# script: open('artifacts/log.txt','w').write(log)  # .txt rejected\n\n# after\n# script: json.dump({'log': log}, open('artifacts/log.json','w'))","handlingStrategy":"type-guard","validationCode":"ALLOWED = {\".csv\", \".html\", \".jpeg\", \".jpg\", \".json\", \".pdf\", \".png\", \".svg\"}\n# in generated code, verify before finishing:\n# for p in pathlib.Path('artifacts').rglob('*'):\n#     assert p.suffix.lower() in ALLOWED, p","typeGuard":"def is_allowed_artifact(name: str) -> bool:\n    return os.path.splitext(name)[1].lower() in {\".csv\", \".html\", \".jpeg\", \".jpg\", \".json\", \".pdf\", \".png\", \".svg\"}","tryCatchPattern":null,"preventionTips":["Constrain generated code to emit only allowlisted file types into artifacts/.","Convert arbitrary outputs (logs, pickles) to .json or .csv in the script.","Keep scratch files outside artifacts/ — only final deliverables go there."],"tags":["artifacts","validation","file-type","tenki"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}