Skip to content

fix(skills): preserve cached sandbox skill paths#6072

Open
stablegenius49 wants to merge 1 commit intoAstrBotDevs:masterfrom
stablegenius49:pr-factory/issue-5922-shipyard-skill-path
Open

fix(skills): preserve cached sandbox skill paths#6072
stablegenius49 wants to merge 1 commit intoAstrBotDevs:masterfrom
stablegenius49:pr-factory/issue-5922-shipyard-skill-path

Conversation

@stablegenius49
Copy link
Contributor

@stablegenius49 stablegenius49 commented Mar 11, 2026

Closes #5922

Modifications / 改动点

  • preserve cached sandbox skill paths for sandbox-only skills instead of always rewriting them to /workspace/skills/...

  • keep the existing fallback for invalid sandbox skill names so prompt-path sanitization still wins on untrusted metadata

  • add regression coverage for both SkillManager.list_skills(runtime="sandbox") and build_skills_prompt(...) using cached absolute sandbox paths

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

Verification steps run locally:

  • python3.11 -m py_compile astrbot/core/skills/skill_manager.py tests/test_skill_metadata_enrichment.py tests/test_skill_manager_sandbox_cache.py
  • focused regression script covering:
    • build_skills_prompt(...) preserves /home/ship_*/workspace/skills/.../SKILL.md for sandbox-only skills
    • invalid sandbox skill names still fall back to /workspace/skills/<invalid_skill_name>/SKILL.md
    • SkillManager.list_skills(runtime="sandbox") keeps the cached absolute sandbox path for cache-only skills

Regression script output:

expanded-regression-check: ok

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

在保持对无效沙盒元数据安全回退机制的同时,保留并正确暴露已缓存的沙盒技能路径。

Bug 修复:

  • 确保提示(prompts)中的仅限沙盒使用的技能在缓存的绝对沙盒路径有效时使用该路径,而不是总是重写为通用的工作区路径。
  • 修复沙盒技能列表逻辑,使其在任何显示选项下都能保留仅限沙盒使用的技能的缓存绝对路径,并在不存在缓存路径时使用安全的默认值。

测试:

  • 添加回归测试,以验证 build_skills_prompt 会保留缓存的绝对沙盒路径,同时仍然会清理无效的沙盒技能名称。
  • 扩展沙盒缓存测试,以确认 SkillManager.list_skills(runtime="sandbox") 会为仅限沙盒使用的技能返回缓存的沙盒路径。
Original summary in English

Summary by Sourcery

Preserve and correctly surface cached sandbox skill paths while maintaining safe fallbacks for invalid sandbox metadata.

Bug Fixes:

  • Ensure sandbox-only skills in prompts use their cached absolute sandbox paths when valid instead of always rewriting to a generic workspace path.
  • Fix sandbox skill listing to retain cached absolute paths for sandbox-only skills regardless of display options, with a safe default when no cached path exists.

Tests:

  • Add regression tests to verify build_skills_prompt preserves cached absolute sandbox paths and still sanitizes invalid sandbox skill names.
  • Extend sandbox cache tests to confirm SkillManager.list_skills(runtime="sandbox") returns the cached sandbox path for sandbox-only skills.

@auto-assign auto-assign bot requested review from Soulter and anka-afk March 11, 2026 18:11
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 11, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where sandbox-only skill paths were being consistently rewritten to a default workspace path, overriding their cached absolute locations. The changes ensure that the system now correctly utilizes and preserves these cached paths for sandbox skills, thereby improving the accuracy and efficiency of skill path resolution. Concurrently, the existing sanitization logic for invalid skill names is maintained to uphold security standards.

Highlights

  • Sandbox Skill Path Preservation: Implemented logic to preserve cached absolute paths for sandbox-only skills in build_skills_prompt, preventing unnecessary rewriting to default workspace paths.
  • Refactored Skill Listing: Refactored list_skills to correctly retrieve and prioritize cached sandbox skill paths, falling back to a default path only if no cached path is available.
  • Enhanced Test Coverage: Added new regression tests to ensure cached sandbox skill paths are preserved and that the existing sanitization logic for invalid skill names remains effective.
Changelog
  • astrbot/core/skills/skill_manager.py
    • Modified build_skills_prompt to conditionally use _sanitize_prompt_path_for_prompt(skill.path) for sandbox-only skills when display_name matches skill.name, falling back to a default path if sanitization fails.
    • Refactored list_skills to first attempt retrieving path_str from sandbox_cached_paths and only construct a default path if no cached path is found.
  • tests/test_skill_manager_sandbox_cache.py
    • Updated the expected path for 'python-sandbox' skill from /workspace/skills/python-sandbox/SKILL.md to /app/skills/python-sandbox/SKILL.md in test_list_skills_merges_local_and_sandbox_cache.
  • tests/test_skill_metadata_enrichment.py
    • Added test_build_skills_prompt_preserves_cached_absolute_sandbox_path to verify that build_skills_prompt correctly preserves absolute sandbox paths for sandbox_only skills.
Activity
  • Provided detailed verification steps and confirmed local test results.
  • Confirmed that the changes are not breaking.
  • Ensured no new dependencies were introduced.
  • Verified that no malicious code was introduced.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the area:core The bug / feature is about astrbot's core, backend label Mar 11, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些整体反馈:

  • 新的 list_skills 逻辑似乎完全忽略了 show_sandbox_path 标志,并且在缓存路径存在时总是优先使用缓存路径;如果你的意图是保留强制使用 /workspace/... 路径的能力,建议要么把这个标志重新接入控制流程,要么移除这个参数以避免混淆。
  • build_skills_prompt 中,仅沙箱(sandbox-only)的回退路径构造在 display_name != skill.name 分支和 not rendered_path 分支中被重复实现;建议提取一个小的辅助函数,或者复用单一代码路径,以减少重复并保持沙箱路径格式的一致性。
  • 新的测试 test_build_skills_prompt_preserves_cached_absolute_sandbox_path 把一个非常具体的绝对路径(/home/ship_0d0fba63/...)写死在代码中;你可能会希望使用更通用或参数化的假绝对路径,以避免让测试与特定用户名或目录结构耦合。
供 AI Agent 使用的提示词
Please address the comments from this code review:

## Overall Comments
- 新的 `list_skills` 逻辑似乎完全忽略了 `show_sandbox_path` 标志,并且在缓存路径存在时总是优先使用缓存路径;如果你的意图是保留强制使用 `/workspace/...` 路径的能力,建议要么把这个标志重新接入控制流程,要么移除这个参数以避免混淆。
-`build_skills_prompt` 中,仅沙箱(sandbox-only)的回退路径构造在 `display_name != skill.name` 分支和 `not rendered_path` 分支中被重复实现;建议提取一个小的辅助函数,或者复用单一代码路径,以减少重复并保持沙箱路径格式的一致性。
- 新的测试 `test_build_skills_prompt_preserves_cached_absolute_sandbox_path` 把一个非常具体的绝对路径(`/home/ship_0d0fba63/...`)写死在代码中;你可能会希望使用更通用或参数化的假绝对路径,以避免让测试与特定用户名或目录结构耦合。

## Individual Comments

### Comment 1
<location path="tests/test_skill_metadata_enrichment.py" line_range="272-289" />
<code_context>
     assert "`/workspace/skills/sandbox-skill/SKILL.md`" in prompt


+def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
+    skills = [
+        SkillInfo(
+            name="docx",
+            description="Read and write Word documents.",
+            path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
+            active=True,
+            source_type="sandbox_only",
+            source_label="sandbox_preset",
+            local_exists=False,
+            sandbox_exists=True,
+        )
+    ]
+
+    prompt = build_skills_prompt(skills)
+
+    assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
+    assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** 通过断言回退的 `/workspace/...` 路径没有出现在 prompt 中来加强回归测试

为了收紧回归测试范围,可以额外断言旧的回退路径在 prompt 中是 *不存在* 的(例如 `"/workspace/skills/docx/SKILL.md" not in prompt`)。这能确保如果实现将路径重新改写回 `/workspace/skills/...`,即使绝对路径仍然偶然存在,测试也会失败。

```suggestion
def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
    skills = [
        SkillInfo(
            name="docx",
            description="Read and write Word documents.",
            path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
            active=True,
            source_type="sandbox_only",
            source_label="sandbox_preset",
            local_exists=False,
            sandbox_exists=True,
        )
    ]

    prompt = build_skills_prompt(skills)

    assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
    assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
    assert "`/workspace/skills/docx/SKILL.md`" not in prompt
```
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据反馈改进后续的审查。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • The new list_skills logic appears to ignore the show_sandbox_path flag entirely and always prefer the cached path when present; if the intent is to preserve the ability to force /workspace/... paths, consider wiring that flag back into the control flow or removing the parameter to avoid confusion.
  • In build_skills_prompt, the sandbox-only fallback path construction is duplicated in both the display_name != skill.name and not rendered_path branches; consider extracting a small helper or reusing a single code path to reduce repetition and keep the sandbox path format consistent.
  • The new test test_build_skills_prompt_preserves_cached_absolute_sandbox_path hardcodes a very specific absolute path (/home/ship_0d0fba63/...); you might want to use a more generic or parametrized fake absolute path to avoid coupling the test to a particular username or directory layout.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `list_skills` logic appears to ignore the `show_sandbox_path` flag entirely and always prefer the cached path when present; if the intent is to preserve the ability to force `/workspace/...` paths, consider wiring that flag back into the control flow or removing the parameter to avoid confusion.
- In `build_skills_prompt`, the sandbox-only fallback path construction is duplicated in both the `display_name != skill.name` and `not rendered_path` branches; consider extracting a small helper or reusing a single code path to reduce repetition and keep the sandbox path format consistent.
- The new test `test_build_skills_prompt_preserves_cached_absolute_sandbox_path` hardcodes a very specific absolute path (`/home/ship_0d0fba63/...`); you might want to use a more generic or parametrized fake absolute path to avoid coupling the test to a particular username or directory layout.

## Individual Comments

### Comment 1
<location path="tests/test_skill_metadata_enrichment.py" line_range="272-289" />
<code_context>
     assert "`/workspace/skills/sandbox-skill/SKILL.md`" in prompt


+def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
+    skills = [
+        SkillInfo(
+            name="docx",
+            description="Read and write Word documents.",
+            path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
+            active=True,
+            source_type="sandbox_only",
+            source_label="sandbox_preset",
+            local_exists=False,
+            sandbox_exists=True,
+        )
+    ]
+
+    prompt = build_skills_prompt(skills)
+
+    assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
+    assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen regression by asserting that the fallback `/workspace/...` path is not present in the prompt

To tighten the regression, also assert that the old fallback path is *absent* from the prompt (e.g. `"/workspace/skills/docx/SKILL.md" not in prompt`). This ensures the test will fail if the implementation ever reverts to rewriting paths back to `/workspace/skills/...`, even if the absolute path is still present incidentally.

```suggestion
def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
    skills = [
        SkillInfo(
            name="docx",
            description="Read and write Word documents.",
            path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
            active=True,
            source_type="sandbox_only",
            source_label="sandbox_preset",
            local_exists=False,
            sandbox_exists=True,
        )
    ]

    prompt = build_skills_prompt(skills)

    assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
    assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
    assert "`/workspace/skills/docx/SKILL.md`" not in prompt
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +272 to +289
def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
skills = [
SkillInfo(
name="docx",
description="Read and write Word documents.",
path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
active=True,
source_type="sandbox_only",
source_label="sandbox_preset",
local_exists=False,
sandbox_exists=True,
)
]

prompt = build_skills_prompt(skills)

assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): 通过断言回退的 /workspace/... 路径没有出现在 prompt 中来加强回归测试

为了收紧回归测试范围,可以额外断言旧的回退路径在 prompt 中是 不存在 的(例如 "/workspace/skills/docx/SKILL.md" not in prompt)。这能确保如果实现将路径重新改写回 /workspace/skills/...,即使绝对路径仍然偶然存在,测试也会失败。

Suggested change
def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
skills = [
SkillInfo(
name="docx",
description="Read and write Word documents.",
path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
active=True,
source_type="sandbox_only",
source_label="sandbox_preset",
local_exists=False,
sandbox_exists=True,
)
]
prompt = build_skills_prompt(skills)
assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
skills = [
SkillInfo(
name="docx",
description="Read and write Word documents.",
path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
active=True,
source_type="sandbox_only",
source_label="sandbox_preset",
local_exists=False,
sandbox_exists=True,
)
]
prompt = build_skills_prompt(skills)
assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
assert "`/workspace/skills/docx/SKILL.md`" not in prompt
Original comment in English

suggestion (testing): Strengthen regression by asserting that the fallback /workspace/... path is not present in the prompt

To tighten the regression, also assert that the old fallback path is absent from the prompt (e.g. "/workspace/skills/docx/SKILL.md" not in prompt). This ensures the test will fail if the implementation ever reverts to rewriting paths back to /workspace/skills/..., even if the absolute path is still present incidentally.

Suggested change
def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
skills = [
SkillInfo(
name="docx",
description="Read and write Word documents.",
path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
active=True,
source_type="sandbox_only",
source_label="sandbox_preset",
local_exists=False,
sandbox_exists=True,
)
]
prompt = build_skills_prompt(skills)
assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
def test_build_skills_prompt_preserves_cached_absolute_sandbox_path():
skills = [
SkillInfo(
name="docx",
description="Read and write Word documents.",
path="/home/ship_0d0fba63/workspace/skills/docx/SKILL.md",
active=True,
source_type="sandbox_only",
source_label="sandbox_preset",
local_exists=False,
sandbox_exists=True,
)
]
prompt = build_skills_prompt(skills)
assert "`/home/ship_0d0fba63/workspace/skills/docx/SKILL.md`" in prompt
assert "cat /home/ship_0d0fba63/workspace/skills/docx/SKILL.md" in prompt
assert "`/workspace/skills/docx/SKILL.md`" not in prompt

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses an issue where cached absolute paths for sandbox-only skills were not being preserved. The changes in build_skills_prompt and list_skills ensure that cached paths are now used, with appropriate fallbacks. The added regression tests provide good coverage for these fixes. I've included one suggestion to refactor a small piece of logic to improve code clarity and reduce duplication.

Comment on lines +157 to +168
if display_name != skill.name:
rendered_path = (
f"{str(SANDBOX_WORKSPACE_ROOT)}/{str(SANDBOX_SKILLS_ROOT)}/"
f"{display_name}/SKILL.md"
)
else:
rendered_path = _sanitize_prompt_path_for_prompt(skill.path)
if not rendered_path:
rendered_path = (
f"{str(SANDBOX_WORKSPACE_ROOT)}/{str(SANDBOX_SKILLS_ROOT)}/"
f"{display_name}/SKILL.md"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for determining rendered_path for sandbox_only skills has some repeated code for constructing the fallback path. You can improve maintainability by defining the fallback path once and reusing it, which also makes the code more concise.

            fallback_path = (
                f"{str(SANDBOX_WORKSPACE_ROOT)}/{str(SANDBOX_SKILLS_ROOT)}/"
                f"{display_name}/SKILL.md"
            )
            if display_name != skill.name:
                rendered_path = fallback_path
            else:
                rendered_path = _sanitize_prompt_path_for_prompt(skill.path) or fallback_path

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]Wrong skill path of system prompt in shipyard sandbox

2 participants