Apply request version to tag-less skill OCI install ref#5078
Merged
Conversation
When POST /api/v1beta/skills receives an OCI reference without an explicit tag or digest (e.g. ghcr.io/stacklok/dockyard/skills/foo) and the JSON body supplies a separate "version" field, parseOCIReference + qualifiedOCIRef defaulted the pull to ":latest" and silently dropped opts.Version, surfacing a misleading "not found" instead of pulling the requested version. Splice opts.Version in as the tag before parsing when the name is OCI-like (contains '/') and has no ':' or '@'. An explicit tag in the name still wins, and registry-resolved refs (which already include a tag) are unaffected.
rdimitrov
approved these changes
Apr 27, 2026
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5078 +/- ##
==========================================
- Coverage 69.82% 69.82% -0.01%
==========================================
Files 564 564
Lines 56649 56653 +4
==========================================
+ Hits 39555 39557 +2
- Misses 14065 14066 +1
- Partials 3029 3030 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POST /api/v1beta/skillswith{"name":"ghcr.io/<org>/<path>/<skill>","version":"0.1.0"}was silently pulling:latestand returning404 Not Foundfrom the upstream registry, even though the JSON body declared the version explicitly.firebase-security-rules-auditorregistry entry whose OCI package identifier isghcr.io/stacklok/dockyard/skills/firebase-security-rules-auditor:0.1.0. Sending the bare repo path +version: 0.1.0(which is what callers building the request from a parsed registry entry naturally do) hit:latest, which is not published.opts.Versioninto the OCI reference as the tag when the caller supplies a tag-less ref. An explicit tag on the name still wins, and registry-resolved refs (which already carry a tag) are unaffected.Fix — splice request version into tag-less OCI refs
pkg/skills/skillsvc/install.go::Installparsesopts.NameviaparseOCIReference, which delegates tonameref.ParseReference. For a tag-less ref likeghcr.io/org/path/skill, go-containerregistry normalizes the parsed reference toname.TagwithIdentifier()=="latest".qualifiedOCIRefthen reproduces that as…/skill:latestfor the pull.opts.Versionwas only used as a fallback to populate the install record's version from the artifact config (lines 105–107 ofinstall_oci.go) — it was never used to qualify the pull URL.The fix splices the version in before the reference is parsed, so
parseOCIReference, the unambiguous-ref check (isUnambiguousOCIRef), andqualifiedOCIRefall observe the fully qualified reference uniformly:Guards:
opts.Version != ""— only act when the caller explicitly asked for a version.strings.ContainsRune(opts.Name, '/')— only act on names that already look OCI-like; plain skill names (firebase-security-rules-auditor) are routed to the registry resolver and must not be silently mutated into OCI refs.!strings.ContainsAny(opts.Name, ":@")— explicit tag/digest in the name wins. This preserves the existing precedence and leaves the registry-resolved path (whereopts.Name = resolved.OCIRef.String()already includes the tag) untouched.Flow after the change
flowchart TD in[POST /skills name+version] --> git{git:// ref?} git -- yes --> gitInstall[installFromGit] git -- no --> tagLess{name has '/' and no ':' or '@'?} tagLess -- yes + version --> splice["opts.Name = name + ':' + version"] tagLess -- no --> parse[parseOCIReference] splice --> parse parse --> isOCI{OCI ref?} isOCI -- yes --> oci[installFromOCI pulls qualifiedOCIRef] isOCI -- no --> name[installByName / registry lookup]Type of change
:latest)Test plan
task buildtask lint-fixthv serve:POST /api/v1beta/skills {"name":"ghcr.io/stacklok/dockyard/skills/firebase-security-rules-auditor","scope":"user","version":"0.1.0"}→pulling OCI artifact "…/firebase-security-rules-auditor:latest": … not found.…/firebase-security-rules-auditor:0.1.0. (For this specific registry entry the install then trips the existing supply-chain check because the published artifact's SKILL.md declares a differentname— that's a publisher-side packaging issue, not a regression.)name: ghcr.io/org/foo:1.2.3+version: 0.1.0→ pulls:1.2.3(no splice because the name contains:).name: firebase-security-rules-auditor+version: 0.1.0→ still routed to the registry resolver (no/in name, splice skipped).installFromResolvedRegistrysetsopts.Name = resolved.OCIRef.String(), which already contains a:, so the splice never fires there.Changes
pkg/skills/skillsvc/install.goopts.Versionin as the tag when the install name is a tag-less OCI-like ref; newstringsimportDoes this introduce a user-facing change?
Yes — bug-fix only. Callers that pass an OCI reference + a separate
versionfield onPOST /api/v1beta/skillsnow actually pull the requested version instead of silently falling through to:latest. Existing callers that already include the tag in the name, or that pass plain skill names through the registry resolver, see no behavioural change.