Skip to content

fix: percent-encode skill file paths in download_skill - #5827

Closed
BlueX888 wants to merge 2 commits into
PrefectHQ:mainfrom
BlueX888:fix/download-skill-uri-encoding
Closed

BlueX888 wants to merge 2 commits into
PrefectHQ:mainfrom
BlueX888:fix/download-skill-uri-encoding

Conversation

@BlueX888

Copy link
Copy Markdown
Contributor

Closes #5826

Description

download_skill() built skill://{skill_name}/{file_info.path} from the server's raw manifest paths without percent-encoding. Because the server unquotes path parameters once, a file named 100%20score.md was fetched as 100 score.md (wrong bytes) and a file named notes#1.md was truncated at the URI fragment (failure or a sibling's content). The server already publishes percent-encoded URIs in SkillProvider._list_resources; the client now requests the same form.

The download URI is built with quote(file_info.path, safe='/'), preserving nested paths. The skill-name segment is deliberately left raw: the server emits and matches it without quoting (SkillProvider._list_resources, _get_resource, _get_resource_template), so encoding it would regress names containing legal sub-delimiters or a literal percent (e.g. pdf!, a&b, 100%20score). A parameterized regression test pins those names.

Contribution type

  • Bug fix (simple, well-scoped fix for a clearly broken behavior)
  • Documentation improvement
  • Enhancement

Bug fix verification

Test path: tests/utilities/test_skills.py::TestDownloadSkillUriEncoding (4 tests).

Before the fix (current main adf90b6):

AssertionError: assert 'DECODED SPACE' == 'LITERAL PERCENT'     # 100%20score.md fetched the decoded sibling
AssertionError: assert 'SIBLING WITHOUT FRAGMENT' == 'HASH FILE'  # notes#1.md fetched notes
FileNotFoundError: ...\team\pdf\100%20#1.md                     # fragment-truncated request
3 failed, 5 passed

After the fix: all eight pass, and independent repro scripts download the correct bytes for percent-triplet, hash-shadow, hash-only, nested-space, unicode, and ordinary nested paths. The parameterized skill-name tests pass on current main as well — they guard against re-introducing name encoding (an intermediate revision quoted the name and failed them).

Validation

  • uv run --frozen pytest tests/utilities/test_skills.py -q -> 34 passed
  • uv run --frozen pytest tests/server/providers/test_skills_provider.py tests/server/providers/test_skills_vendor_providers.py tests/server/providers/test_skill_main_files.py -q -> 120 passed
  • uv run --frozen ruff check and ruff format --check on the changed files -> clean

AI assistance

Tool(s) used: Claude Code
How you used it: investigated from a verified reproduction, drafted the fix and regression tests.

Checklist

  • This PR addresses an existing issue (or fixes a self-evident bug)
  • I have read CONTRIBUTING.md
  • I have added tests that cover my changes
  • I have run uv run prek run --all-files and all checks pass
  • I have self-reviewed my changes
  • If I used an LLM, it followed the repo's contributing conventions (not generic output)

download_skill built resource URIs from raw manifest paths. Because the
server unquotes path parameters once, a path such as '100%20score.md'
was requested as '100 score.md' and the wrong file's bytes were written;
a path containing '#' was truncated at the URI fragment, failing or
reading a sibling. Quote skill names and file paths with safe='/' so the
URI matches the one SkillProvider lists.

Add regression tests for percent-triplet and '#' file names, nested
paths, and the exact requested URIs.
The server emits the skill-name segment raw and matches it before any
unquoting, so percent-encoding it broke downloads for names containing
legal sub-delimiters or a literal percent (pdf!, a&b, plus+name,
100%20score). Quote only the file path, which is what the server
actually encodes and unquotes.

Add a parameterized regression test for skill names containing those
characters.
@github-actions

Copy link
Copy Markdown
Contributor

This pull request is awaiting maintainer assignment. Linked PRs stay open while maintainers triage the issue; this check remains failing until assignment.

Per CONTRIBUTING.md, an external PR must reference an issue that's assigned to its author. To get there:

  1. A maintainer will review the issue and decide whether to assign you. Please don't comment just to request assignment.

Once you're assigned and the link is present, this check is re-run automatically. Previously gate-closed PRs also reopen — no further action needed.

Maintainers: reopen this PR or remove the missing-issue-link label to bypass this check.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T03:16:02.162370Z 27e72e4 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@marvin-context-protocol marvin-context-protocol Bot added the bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. label Oct 19, 2026

Copy link
Copy Markdown
Collaborator

thanks! this overlaps with #5820, which covers the same skill-path encoding and conflicts with this branch, so i'm closing this one to keep it to a single PR.


Generated by Claude Code

@zzstoatzz zzstoatzz closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. missing-issue-link

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] download_skill interpolates raw manifest paths into skill:// URIs, fetching the wrong file

2 participants