[Bug] 沙盒缓存残留导致内置 star 提供的 skill(如 pdf)被误判为 sandbox preset,WebUI 无法查看

Open Beginner friendly
#9,754 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
75/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
python
Domain
backend

Research direction

Start with SkillManager.is_sandbox_only_skill() in astrbot/core/skills/skill_manager.py and trace its use from SkillsService.resolve_local_skill_dir() in astrbot/dashboard/services/skills_service.py. Reproduce the sandbox-to-local switch with a cached pdf entry, then verify that plugin-provided skills such as pdf can be opened read-only without the sandbox preset error.

Written by the indexing model from the issue text.

Description

area:core area:webui bug
问题描述

SkillManager.is_sandbox_only_skill()astrbot/core/skills/skill_manager.py:683)存在两处缺陷,叠加导致内置 star skill 无法在 WebUI 查看:

  1. 只检查本地 skills_rootdata/sandbox_skills_cache.json,从不检查 plugin-provided skills。内置 star astrbot 自带的 skills/pdfdocumentsspreadsheetsskill-creator 属于 plugin skill(source_type="plugin",只读可查看);
  2. 读取沙盒缓存时不区分当前 computer_use_runtime。该缓存是持久文件,仅在新沙盒会话发现 skills 时更新,切回 local 后不会清理。

因此只要曾经配置过沙盒(缓存中已包含同名条目),之后即使切回 local 运行时,SkillsService.resolve_local_skill_dir()astrbot/dashboard/services/skills_service.py:123)会先命中该检查并抛出:

PermissionError: Sandbox preset skill cannot be opened from local skill files.

前端按 source_type="plugin" 预期该 skill 可只读查看,结果收到错误。

背景:此前上传过名为 pdf 的本地 skill,与后来版本新增的内置 astrbot star 的 skills/pdf 同名。list_skills 中本地 skill 优先、内置被跳过(重名遮蔽);删除本地同名 skill 后内置 pdf 浮现,随即触发本问题。

建议修复:is_sandbox_only_skill() 开头增加

if self._get_plugin_skill_dir(name) is not None:
    return False

Workaround:删除 data/sandbox_skills_cache.json(纯缓存,local 运行时无用,下次沙盒会话会重新生成)。

如何复现?
  1. 配置 computer_use_runtime 为 sandbox 并运行,使 data/sandbox_skills_cache.json 生成(内含 pdf 等内置 skill 条目)
  2. 切回 local 运行时(缓存保留)
  3. 确保 data/skills/pdf 不存在(全新安装即满足;或曾上传同名本地 skill 后删除)
  4. WebUI → 扩展 → 技能,点击内置 pdf skill 查看文件
  5. 报错:Sandbox preset skill cannot be opened from local skill files.
AstrBot 版本

v4.27.4

操作系统

Linux

部署方式

Docker

使用的消息平台适配器

无关

错误日志
[16:04:43.151] [Core] [ERRO] [v4.27.4] [api.skills:80]: Sandbox preset skill cannot be opened from local skill files.
Traceback (most recent call last):

  File "/AstrBot/main.py", line 155, in <module>
    asyncio.run(main_async(args.webui_dir))
    │       │   │          │    └ None
    │       │   │          └ Namespace(webui_dir=None, reset_password=False)
    │       │   └ <function main_async at 0x7fb60d14ab60>
    │       └ <function run at 0x7fb610611a80>
    └ <module 'asyncio' from '/usr/local/lib/python3.12/asyncio/__init__.py'>

  File "/usr/local/lib/python3.12/asyncio/runners.py", line 195, in run
    return runner.run(main)
           │      │   └ <coroutine object main_async at 0x7fb60355a880>
           │      └ <function Runner.run at 0x7fb61044af20>
           └ <asyncio.runners.Runner object at 0x7fb602bb4f50>
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           │    │     │                  └ <Task pending name='Task-1' coro=<main_async() running at /AstrBot/main.py:127> wait_for=<_GatheringFuture pending cb=[Task.t...
           │    │     └ <function BaseEventLoop.run_until_complete at 0x7fb610448b80>
           │    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
           └ <asyncio.runners.Runner object at 0x7fb602bb4f50>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 678, in run_until_complete
    self.run_forever()
    │    └ <function BaseEventLoop.run_forever at 0x7fb610448ae0>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 645, in run_forever
    self._run_once()
    │    └ <function BaseEventLoop._run_once at 0x7fb61044a8e0>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 1999, in _run_once
    handle._run()
    │      └ <function Handle._run at 0x7fb6105b0c20>
    └ <Handle Task.task_wakeup(<Future finis...7fb5d63d4230>>)>
  File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
    │    │            │    │           │    └ <member '_args' of 'Handle' objects>
    │    │            │    │           └ <Handle Task.task_wakeup(<Future finis...7fb5d63d4230>>)>
    │    │            │    └ <member '_callback' of 'Handle' objects>
    │    │            └ <Handle Task.task_wakeup(<Future finis...7fb5d63d4230>>)>
    │    └ <member '_context' of 'Handle' objects>
    └ <Handle Task.task_wakeup(<Future finis...7fb5d63d4230>>)>
  File "/usr/local/lib/python3.12/site-packages/anyio/_core/_tasks.py", line 275, in _run_coro
    retval = await self._coro
                   │    └ <member '_coro' of 'TaskHandle' objects>
                   └ <TaskHandle pending name='starlette.middleware.base.BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.coro' coro=<corou...
  File "/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py", line 144, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
          │    │   │      │                      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.send_no_error at 0x7fb5ba468540>
          │    │   │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │    │   └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │    └ <starlette.middleware.exceptions.ExceptionMiddleware object at 0x7fb5d6283b60>
          └ <starlette.middleware.base.BaseHTTPMiddleware object at 0x7fb5d623e750>
  File "/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
          │                            │    │    │     │      │        └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.send_no_error at 0x7fb5ba468540>
          │                            │    │    │     │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │                            │    │    │     └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │                            │    │    └ <starlette.requests.Request object at 0x7fb5b9d32c30>
          │                            │    └ <fastapi.middleware.asyncexitstack.AsyncExitStackMiddleware object at 0x7fb5d623f200>
          │                            └ <starlette.middleware.exceptions.ExceptionMiddleware object at 0x7fb5d6283b60>
          └ <function wrap_app_handling_exceptions at 0x7fb607ff47c0>
  File "/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
          │   │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │   │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │   └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          └ <fastapi.middleware.asyncexitstack.AsyncExitStackMiddleware object at 0x7fb5d623f200>
  File "/usr/local/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
          │    │   │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │    │   │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │    │   └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │    └ <fastapi.routing.APIRouter object at 0x7fb5d63a7ad0>
          └ <fastapi.middleware.asyncexitstack.AsyncExitStackMiddleware object at 0x7fb5d623f200>
  File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 670, in __call__
    await self.middleware_stack(scope, receive, send)
          │    │                │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │    │                │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │    │                └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │    └ <bound method APIRouter.app of <fastapi.routing.APIRouter object at 0x7fb5d63a7ad0>>
          └ <fastapi.routing.APIRouter object at 0x7fb5d63a7ad0>
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 2734, in app
    await route.handle(scope, receive, send)
          │     │      │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │     │      │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │     │      └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │     └ <function _IncludedRouter.handle at 0x7fb6037079c0>
          └ _IncludedRouter(original_router=<fastapi.routing.APIRouter object at 0x7fb5d63d6750>, include_context=_RouterIncludeContext(i...
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 1780, in handle
    await self.original_router.handle(scope, receive, send)
          │    │               │      │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │    │               │      │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │    │               │      └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │    │               └ <function APIRouter.handle at 0x7fb60372da80>
          │    └ <fastapi.routing.APIRouter object at 0x7fb5d63d6750>
          └ _IncludedRouter(original_router=<fastapi.routing.APIRouter object at 0x7fb5d63d6750>, include_context=_RouterIncludeContext(i...
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 2789, in handle
    await included_router._handle_selected(scope, receive, send)
          │               │                │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │               │                │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │               │                └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │               └ <function _IncludedRouter._handle_selected at 0x7fb603707a60>
          └ _IncludedRouter(original_router=<fastapi.routing.APIRouter object at 0x7fb5d63d6750>, include_context=_RouterIncludeContext(i...
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 1791, in _handle_selected
    await route.handle(scope, receive, send)
          │     │      │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │     │      │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │     │      └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │     └ <function _IncludedRouter.handle at 0x7fb6037079c0>
          └ _IncludedRouter(original_router=<fastapi.routing.APIRouter object at 0x7fb602c43d40>, include_context=_RouterIncludeContext(i...
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 1780, in handle
    await self.original_router.handle(scope, receive, send)
          │    │               │      │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │    │               │      │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │    │               │      └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │    │               └ <function APIRouter.handle at 0x7fb60372da80>
          │    └ <fastapi.routing.APIRouter object at 0x7fb602c43d40>
          └ _IncludedRouter(original_router=<fastapi.routing.APIRouter object at 0x7fb602c43d40>, include_context=_RouterIncludeContext(i...
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 2789, in handle
    await included_router._handle_selected(scope, receive, send)
          │               │                │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │               │                │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │               │                └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │               └ <function _IncludedRouter._handle_selected at 0x7fb603707a60>
          └ _IncludedRouter(original_router=<fastapi.routing.APIRouter object at 0x7fb602c43d40>, include_context=_RouterIncludeContext(i...
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 1800, in _handle_selected
    await original_route.handle(scope, receive, send)
          │              │      │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │              │      │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │              │      └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │              └ <function APIRoute.handle at 0x7fb603705a80>
          └ APIRoute(path='/skills/files', name='list_skill_files_by_name', methods=['GET'])
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 1279, in handle
    await app(scope, receive, send)
          │   │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │   │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │   └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          └ <function request_response.<locals>.app at 0x7fb5b9d04040>
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 158, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
          │                            │    │        │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d04f40>
          │                            │    │        │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │                            │    │        └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          │                            │    └ <starlette.requests.Request object at 0x7fb5b9d31b50>
          │                            └ <function request_response.<locals>.app.<locals>.app at 0x7fb5b9d04400>
          └ <function wrap_app_handling_exceptions at 0x7fb607ff47c0>
  File "/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
          │   │      │        └ <function wrap_app_handling_exceptions.<locals>.wrapped_app.<locals>.sender at 0x7fb5b9d072e0>
          │   │      └ <function BaseHTTPMiddleware.__call__.<locals>.call_next.<locals>.receive_or_disconnect at 0x7fb5ba46a520>
          │   └ {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http',...
          └ <function request_response.<locals>.app.<locals>.app at 0x7fb5b9d04400>
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 144, in app
    response = await f(request)
                     │ └ <starlette.requests.Request object at 0x7fb5b9d31b50>
                     └ <function get_request_handler.<locals>.app at 0x7fb5b9d049a0>
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 706, in app
    raw_response = await run_endpoint_function(
                         └ <function run_endpoint_function at 0x7fb603704e00>
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 352, in run_endpoint_function
    return await dependant.call(**values)
                 │         │      └ {'_auth': AuthContext(username='rain1', scopes=['*'], api_key_id=None, via='jwt'), 'service': <astrbot.dashboard.services.ski...
                 │         └ <member 'call' of 'Dependant' objects>
                 └ Dependant(path_params=[], query_params=[ModelField(field_info=Query(PydanticUndefined), name='skill_name', mode='validation',...

  File "/AstrBot/astrbot/dashboard/api/skills.py", line 181, in list_skill_files_by_name
    return await _run(
                 └ <function _run at 0x7fb602c4c9a0>

> File "/AstrBot/astrbot/dashboard/api/skills.py", line 73, in _run
    result = await run_maybe_async(operation)
                   │               └ <function list_skill_files_by_name.<locals>.<lambda> at 0x7fb5b9f63ba0>
                   └ <function run_maybe_async at 0x7fb60330ba60>

  File "/AstrBot/astrbot/dashboard/async_utils.py", line 19, in run_maybe_async
    result: Any = operation() if callable(operation) else operation
                  │                       │               └ <function list_skill_files_by_name.<locals>.<lambda> at 0x7fb5b9f63ba0>
                  │                       └ <function list_skill_files_by_name.<locals>.<lambda> at 0x7fb5b9f63ba0>
                  └ <function list_skill_files_by_name.<locals>.<lambda> at 0x7fb5b9f63ba0>

  File "/AstrBot/astrbot/dashboard/api/skills.py", line 182, in <lambda>
    lambda: service.list_skill_files(
            │       └ <function SkillsService.list_skill_files at 0x7fb6030ea200>
            └ <astrbot.dashboard.services.skills_service.SkillsService object at 0x7fb5d63d4230>

  File "/AstrBot/astrbot/dashboard/services/skills_service.py", line 502, in list_skill_files
    skill_dir = self.resolve_local_skill_dir(skill_name)
                │    │                       └ 'pdf'
                │    └ <function SkillsService.resolve_local_skill_dir at 0x7fb6030e9a80>
                └ <astrbot.dashboard.services.skills_service.SkillsService object at 0x7fb5d63d4230>

  File "/AstrBot/astrbot/dashboard/services/skills_service.py", line 132, in resolve_local_skill_dir
    raise PermissionError(

PermissionError: Sandbox preset skill cannot be opened from local skill files.
辅助信息

No response

检查清单
  • 我已在 Issue 列表中搜索过相关问题仍无法解决,或该问题从未被报告过。
  • 我已尝试过禁用所有插件,排除了可能是因插件导致的问题。
  • 我报告的问题与 AstrBot 本体相关,而非在报告某一插件的问题。
  • 我已阅读并同意本项目的贡献者行为准则
  • (可选)我愿意提交 PR 以帮助修复该问题。
Dominant language
Python
Stars
40.7k
Forks
2.9k
Avg merge
1d 5h
Merged PRs (30d)
112

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from AstrBotDevs/AstrBot

All issues in AstrBotDevs/AstrBot

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.