Skip to content

Fix skill install for qualified namespace/name references and missing parent directory#4841

Open
samuv wants to merge 5 commits intomainfrom
skill-install-fallback
Open

Fix skill install for qualified namespace/name references and missing parent directory#4841
samuv wants to merge 5 commits intomainfrom
skill-install-fallback

Conversation

@samuv
Copy link
Copy Markdown
Contributor

@samuv samuv commented Apr 15, 2026

Summary

  • Fix Install to fall back to registry catalog lookup when a namespace/name reference (e.g. io.github.stacklok/skill-creator) fails OCI pull because the namespace is parsed as a registry host. Names with an explicit tag or digest (e.g. ghcr.io/org/skill:v1) or more than one / (e.g. ghcr.io/org/skill) are unambiguously OCI and do not fall back
  • Extract installFromResolvedRegistry helper to share dispatch logic between installFromRegistryLookup and the new fallback path
  • Fix WriteFiles failing to acquire the lock when the parent skills directory does not yet exist (e.g. first install to ~/.cline/skills/)
  • Fix pre-existing bug in installFromResolvedRegistry OCI branch: pass result.Skill.Metadata.Name instead of opts.Name (the OCI ref string) to installAndRegister, so group registration and rollback use the correct skill name

Behavioral change note

Previously, a registry entry that existed but had no installable OCI or git package fell through and returned 404 Not Found. It now returns 422 Unprocessable Entity ("resolved but no installable package"), which is a more accurate response.

Type of change

  • Bug fix

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)

Changes

File Change
pkg/skills/skillsvc/skillsvc.go Add OCI→registry fallback in Install and GetContent; tighten guard to also block multi-segment refs; add debug log before fallback; fix opts.Name bug in OCI branch of installFromResolvedRegistry
pkg/skills/skillsvc/skillsvc_test.go Extend TestInstallQualifiedNameOCIFallback with digest, multi-segment, and ambiguity cases; pin Pull mock arguments
pkg/skills/gitresolver/writer.go MkdirAll parent before acquiring file lock
pkg/skills/gitresolver/writer_test.go Add test for missing parent directory

Does this introduce a user-facing change?

Yes — thv skill install io.github.stacklok/<skill-name> now works correctly instead of failing with "no such host".

@samuv samuv requested a review from JAORMX as a code owner April 15, 2026 10:24
@github-actions github-actions bot added the size/S Small PR: 100-299 lines changed label Apr 15, 2026
Comment thread pkg/skills/gitresolver/writer.go Dismissed
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 77.77778% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.24%. Comparing base (ab2a821) to head (ce7d910).

Files with missing lines Patch % Lines
pkg/skills/skillsvc/skillsvc.go 82.35% 5 Missing and 1 partial ⚠️
pkg/skills/gitresolver/writer.go 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4841      +/-   ##
==========================================
+ Coverage   69.19%   69.24%   +0.04%     
==========================================
  Files         533      533              
  Lines       55280    55296      +16     
==========================================
+ Hits        38253    38288      +35     
+ Misses      14083    14064      -19     
  Partials     2944     2944              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread pkg/skills/skillsvc/skillsvc.go Outdated
Comment thread pkg/skills/skillsvc/skillsvc.go
Copy link
Copy Markdown
Collaborator

@JAORMX JAORMX left a comment

Choose a reason for hiding this comment

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

Hey! Nice fix. The core approach is solid... the :@ heuristic to distinguish "ambiguous namespace/name" from "unambiguous OCI ref with tag/digest" is clean and easy to reason about. The installFromResolvedRegistry extraction is a good refactor, and the writer.go fix addresses a real first-install bug.

I verified the locking story and it's fine. The fallback path in Install() never holds a lock when calling downstream installers, so no deadlock risk. InstallOptions is passed by value and slice fields are replaced wholesale, not mutated in place. All good there.

A few things I'd like addressed before merging:

The multi-segment OCI edge case. A ref like myregistry.com/org/skill (3 segments, no tag) passes the guard and triggers the fallback. The namespace filtering makes a false match unlikely, but it's a supply-chain confusion vector in principle. Adding || strings.Count(opts.Name, "/") > 1 to the guard closes this cleanly. See inline comment.

The writer.go fix needs a test. All existing WriteFiles tests pre-create the parent directory, so no test actually proves this fix works. A test with a non-existent parent would take ~10 lines and directly exercise the bugfix.

CodeQL alert. The #nosec / lgtm annotations won't suppress CodeQL (different tool). This needs to be dismissed in GitHub's Security tab.

The rest are nits and nice-to-haves (logging the original OCI error before fallback, noting the 404->422 behavioral change in the PR description, tightening gomock.Any() in tests). See inline comments for details.

Comment thread pkg/skills/skillsvc/skillsvc.go Outdated
Comment thread pkg/skills/skillsvc/skillsvc.go
Comment thread pkg/skills/skillsvc/skillsvc.go
Comment thread pkg/skills/gitresolver/writer.go
Comment thread pkg/skills/skillsvc/skillsvc_test.go
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed size/M Medium PR: 300-599 lines changed and removed size/S Small PR: 100-299 lines changed labels Apr 15, 2026
@samuv samuv self-assigned this Apr 15, 2026
@samuv samuv requested a review from JAORMX April 15, 2026 12:59
samuv added 5 commits April 16, 2026 19:05
Extend the fallback guard in Install and GetContent to also reject
refs with more than one '/' (e.g. ghcr.io/org/skill). Previously only
':' and '@' were checked, so a tagless multi-segment OCI reference
could silently trigger a registry lookup on pull failure.

Add a Debug log line before the fallback so operators can see when
the fallback path is taken.

Fix installFromResolvedRegistry passing opts.Name (the OCI reference
string) to installAndRegister instead of result.Skill.Metadata.Name,
which caused incorrect group entries and broken rollback.
…ases

Add three new test cases to TestInstallQualifiedNameOCIFallback:
- digest ref (@sha256:...) must not trigger registry fallback
- multi-segment OCI ref (ghcr.io/org/skill) must not trigger fallback
- multiple registry matches must surface a conflict error to the caller

Also pin the Pull mock's third argument in the three qualifying-name
cases to the expected reference string instead of gomock.Any().
@samuv samuv force-pushed the skill-install-fallback branch from bb08ce8 to ce7d910 Compare April 16, 2026 17:05
@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Medium PR: 300-599 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants