Conversation
There was a problem hiding this comment.
Large PR Detected
This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.
How to unblock this PR:
Add a section to your PR description with the following format:
## Large PR Justification
[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformationAlternative:
Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.
See our Contributing Guidelines for more details.
This review will be automatically dismissed once you add the justification section.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4338 +/- ##
==========================================
+ Coverage 68.95% 69.33% +0.37%
==========================================
Files 479 479
Lines 48489 48592 +103
==========================================
+ Hits 33438 33692 +254
+ Misses 12317 12304 -13
+ Partials 2734 2596 -138 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
a1a0eea to
cad0c77
Compare
cad0c77 to
a08fde1
Compare
|
✅ Large PR justification has been provided. The size review has been dismissed and this PR can now proceed with normal review. |
Large PR justification has been provided. Thank you!
a08fde1 to
e8f046f
Compare
aponcedeleonch
left a comment
There was a problem hiding this comment.
Review Comments
Potential inconsistent state on group registration failure
In Install() (skillsvc.go:221-226), if installFromGit succeeds (files written + DB record created) but registerSkillInGroup fails, the skill is installed on disk and in the DB but not added to the group. This leaves the system in an inconsistent state — the skill exists but isn't in the expected group.
Note: this is the same pattern used by the OCI install path, so it's not a regression introduced by this PR — but worth tracking as a follow-up improvement for both paths.
Missing unit test coverage
- No unit tests for
validateHostwith the SSRF bypass enabled/disabled (both branches ofisDevMode()). Since this is security-critical code, it would be good to have tests usingt.Setenv("TOOLHIVE_DEV", "true")and without it to verify the SSRF protection logic works correctly in production configuration. - No unit tests for
buildGitReferenceFromRegistryURLedge cases (e.g.,http://URL silent promotion tohttps://, already prefixed withgit://).
pkg/skills/skillsvc/skillsvc.go
Outdated
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return result, s.registerSkillInGroup(ctx, opts.Group, result.Skill.Metadata.Name) |
There was a problem hiding this comment.
If installFromGit succeeds (files written + DB record created) but registerSkillInGroup fails, the skill is installed on disk and in the DB but not added to the group. This leaves the system in an inconsistent state — the skill exists but isn't in the expected group.
Note: this is the same pattern used by the OCI install path, so it's not a regression introduced by this PR — but worth tracking as a follow-up improvement for both paths.
There was a problem hiding this comment.
Good catch — addressed this directly rather than deferring. Added installAndRegister helper that wraps registerSkillInGroup with a best-effort DB rollback (store.Delete) on failure. This covers all 5 call sites (git direct, OCI direct, installByName, and both registry lookup paths).
If group registration fails, the DB record is removed so a retry starts fresh rather than leaving an orphaned skill record. Files on disk are left in place (a retry with the same content will detect them).
Also added TestInstallFromGitGroupRegistrationRollback to verify the rollback behavior on the git install path, and updated the existing "group registration error" test to assert the Delete call.
| // git HTTP servers. | ||
| func isDevMode() bool { | ||
| return strings.EqualFold(os.Getenv("TOOLHIVE_DEV"), "true") | ||
| } |
There was a problem hiding this comment.
Missing unit tests for validateHost covering both branches of isDevMode(). Since this is security-critical SSRF protection, it would be good to have tests using t.Setenv("TOOLHIVE_DEV", "true") and without it to verify the protection works correctly in production configuration.
Also missing: unit tests for buildGitReferenceFromRegistryURL edge cases (e.g., http:// URL silent promotion to https://, already prefixed with git://).
There was a problem hiding this comment.
Added both:
TestParseGitReferenceDevMode in reference_test.go — covers validateHost with TOOLHIVE_DEV=true:
- 5 success cases: localhost, 127.0.0.1, 10.x, 192.168.x, and localhost-with-port all allowed in dev mode (returning
http://URLs) - 3 error cases: empty host, no repo path, single path component still rejected in dev mode
- Uses
t.Setenvin a separate non-parallel test function (//nolint:paralleltest)
TestBuildGitReferenceFromRegistryURL in skillsvc_test.go — covers edge cases:
https://→git://conversion (happy path)http://silent promotion togit://git://pass-throughhttps://with nested path/ref/fragment- Error cases: empty git ref, unsupported
ftp://scheme, bare string without scheme, missing repo path
The gitresolver package was fully implemented but not connected to the main install path. This change integrates it so users can install skills from git repositories via direct references or registry fallback. Changes: - Add WithGitResolver option and installFromGit method to skillsvc - Detect git:// prefix in Install() before OCI reference check - Extend resolveFromRegistry to handle "git" package types alongside OCI - Add dev-mode SSRF bypass in validateHost for E2E testability - Generate mock for gitresolver.Resolver interface - Add unit tests for all git install paths (fresh, upgrade, no-op, errors) - Add E2E tests with local dumb-HTTP git server infrastructure Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract helpers to bring Install, installFromGit, and resolveFromRegistry below the gocyclo threshold of 15: - installByName: plain-name install flow from Install - installFromRegistryLookup: registry resolution dispatch - applyGitInstall: store check + create/upgrade branching - writeAndPersistGitSkill: shared write + persist logic - resolveRegistryPackages: OCI/git package selection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If registerSkillInGroup fails after a successful install, the skill was left installed on disk and in the DB but not added to the expected group. Add installAndRegister helper that wraps all five call sites with a best-effort store.Delete rollback so retries start fresh. Add unit tests for validateHost dev-mode SSRF bypass, git reference URL conversion edge cases, and group registration rollback behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
e8f046f to
3106985
Compare
|
Rebased onto main and addressed all review feedback in 3106985: Inconsistent state fix — Rather than deferring, fixed it directly. Added New tests:
|
Fixes goconst lint violation where the same hash string appeared in three test functions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
The gitresolver package was fully implemented but had no connection to the skill install path. Users couldn't actually install skills from git repositories — the resolver sat unused.
This change integrates the git resolver so
thv skill installsupports git-based skill sources, both via explicitgit://prefix and through registry entries that specify a git package type.WithGitResolveroption andinstallFromGitmethod to skillsvc to handle git-based installsgit://prefix inInstall()before falling through to OCI reference checkresolveFromRegistryto handle"git"package types alongside OCIvalidateHostfor E2E testabilitygitresolver.ResolverinterfaceType of change
Test plan
task test)task test-e2e)Changes
pkg/api/server.gogitresolver.NewResolver()into server builderpkg/skills/gitresolver/reference.goIsGitReferencehelper, exportParseReferencepkg/skills/gitresolver/resolver.goResolverinterface declaration for mock generationpkg/skills/gitresolver/mocks/mock_resolver.goResolverinterfacepkg/skills/skillsvc/skillsvc.goWithGitResolver,installFromGit, git-aware registry resolutionpkg/skills/skillsvc/skillsvc_test.gotest/e2e/api_skills_git_test.goDoes this introduce a user-facing change?
Yes — users can now install skills from git repositories using
thv skill install git://or via registry entries with a git package type.Large PR Justification
gitresolver.Resolver(57 lines)Special notes for reviewers
validateHostfunction has a dev-mode bypass (THV_DEV_SKIP_SSRF_CHECK) to allow E2E tests to use localhost git servers. This is gated behind a build/env check and never active in production.Generated with Claude Code