GitRepository no longer works with 1.5.0
After upgrading to 1.5.0 gitrepository resources fail with error failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: "git": executable file not found in $PATH'
what might be not standard here is custom port used for git access and recurseSubmodules: true
The git binary is not present in 1.4.1 either, that's because we use gogit for talking the Git protocol, purely in Go.
docker run -it --rm --entrypoint /bin/sh ghcr.io/fluxcd/source-controller:v1.4.1
~ $ git
/bin/sh: git: not found
~ $ exit
docker run -it --rm --entrypoint /bin/sh ghcr.io/fluxcd/source-controller:v1.5.0
~ $ git
/bin/sh: git: not found
~ $ exit
So it looks like there is some incorrect reference introduced in code?
Can you please share your manifests?
I think combination here is: recurseSubmodules: true + custom port in url
kind: GitRepository
metadata:
name: example
namespace: flux-system
spec:
interval: 1m
recurseSubmodules: true
url: ssh://[email protected]:1234/repo.git
secretRef:
name: sshkey
OK, I have easy reproduction path: Please check this repository: https://github.com/Szpadel/flux-repr/ You need to have this manifest:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: example
namespace: flux-system
spec:
interval: 1m
recurseSubmodules: true
ref:
branch: main
url: https://github.com/Szpadel/flux-repr.git
and set gitmodule with relative path:
[submodule "example"]
path = example
url = ../chrome-headless-render-pdf
I suspect it's caused by this code: https://github.com/go-git/go-git/blob/main/plumbing/transport/file/client.go Relevant: https://github.com/go-git/go-git/issues/339
I do not believe those logs are useful, because it looks like real failure is masked by flux
{"level":"error","ts":"2025-06-16T06:53:46.777Z","msg":"failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: \"git\": executable file not found in $PATH","name":"cluster-config","namespace":"flux-system","reconciler kind":"GitRepository","annotations":null,"error":"GitOperationFailed","stacktrace":"github.com/fluxcd/pkg/runtime/events.(*Recorder).AnnotatedEventf\n\tgithub.com/fluxcd/pkg/[email protected]/events/recorder.go:179\ngithub.com/fluxcd/pkg/runtime/events.(*Recorder).Eventf\n\tgithub.com/fluxcd/pkg/[email protected]/events/recorder.go:143\ngithub.com/fluxcd/source-controller/internal/reconcile/summarize.recordEvent\n\tgithub.com/fluxcd/source-controller/internal/reconcile/summarize/processor.go:101\ngithub.com/fluxcd/source-controller/internal/reconcile/summarize.ErrorActionHandler\n\tgithub.com/fluxcd/source-controller/internal/reconcile/summarize/processor.go:58\ngithub.com/fluxcd/source-controller/internal/reconcile/summarize.(*Helper).SummarizeAndPatch\n\tgithub.com/fluxcd/source-controller/internal/reconcile/summarize/summary.go:193\ngithub.com/fluxcd/source-controller/internal/controller.(*GitRepositoryReconciler).Reconcile.func1\n\tgithub.com/fluxcd/source-controller/internal/controller/gitrepository_controller.go:211\ngithub.com/fluxcd/source-controller/internal/controller.(*GitRepositoryReconciler).Reconcile\n\tgithub.com/fluxcd/source-controller/internal/controller/gitrepository_controller.go:248\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Reconcile\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:119\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:340\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:300\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:202"}
{"level":"debug","ts":"2025-06-16T06:53:46.779Z","logger":"events","msg":"failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: \"git\": executable file not found in $PATH","type":"Warning","object":{"kind":"GitRepository","namespace":"flux-system","name":"cluster-config","uid":"ff6be494-5922-40a8-9d11-4e6d0b6194de","apiVersion":"source.toolkit.fluxcd.io/v1","resourceVersion":"34193087"},"reason":"GitOperationFailed"}
{"level":"error","ts":"2025-06-16T06:53:46.810Z","msg":"Reconciler error","controller":"gitrepository","controllerGroup":"source.toolkit.fluxcd.io","controllerKind":"GitRepository","GitRepository":{"name":"cluster-config","namespace":"flux-system"},"namespace":"flux-system","name":"cluster-config","reconcileID":"c6feeca9-d69c-4d22-9c50-2342487cbd5f","error":"failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: \"git\": executable file not found in $PATH","stacktrace":"sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:353\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:300\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:202"}
The GitRepo above reconciles successfully for me in Flux 2.4.0:
k get fluxinstance flux
NAME AGE READY STATUS REVISION
flux 87s True Reconciliation finished in 1s v2.4.0@sha256:e8b07973149a4b84d946be222086ac8289c241ff80fa2ce39a28c62c577fa6fb
k get gitrepo
NAME URL AGE READY STATUS
example https://github.com/Szpadel/flux-repr.git 56s True stored artifact for revision 'main@sha1:791b1e0e8c5e444da9cc3c69d2e7fb08329f094b'
And fails with exec: "git": executable file not found in $PATH in Flux 2.5.1:
k get fluxinstance flux
NAME AGE READY STATUS REVISION
flux 8m35s True Reconciliation finished in 1s v2.5.1@sha256:adf61999819254cbbf77f7a7aa6427c8cc63d1a1a94f2d169a9ac11d96fcf6bb
k get gitrepo
NAME URL AGE READY STATUS
example https://github.com/Szpadel/flux-repr.git 8s False failed to checkout and determine revision: unable to clone 'https://github.com/Szpadel/flux-repr.git': exec: "git": executable file not found in $PATH
This is indeed a regression in SC from 1.4.0 to 1.5.0.
@pjbgf Maybe you have some insights? I don't think we changed anything drastically in this code path in Flux from 2.4 to 2.5
Additional Investigation: go-git CVE Fix Breaking Submodule Relative URLs
Hi 👋 I had some time and got curious about this issue, so I did some digging on my end. Thought I'd share what I found in case it helps:
Working vs Non-working Versions
- Flux 2.4.0 (go-git v5.12.0): Submodules work correctly
- Flux 2.5.1 (go-git v5.13.2): Submodules fail to clone
- This indicates a regression between these versions
Problem Identified: Relative Submodule URLs
I forked @Szpadel's reproduction repository and confirmed the issue is specifically with relative submodule URLs:
Original repo (fails): https://github.com/Szpadel/flux-repr
Fixed repo (works): https://github.com/cappyzawa/flux-repr
Change required:
[submodule "subdir"]
path = subdir
- url = ../flux-repr-sub
+ url = https://github.com/Szpadel/flux-repr-sub
Results:
- ❌ Relative URLs: GitRepository fails to become Ready
- ✅ Absolute URLs: GitRepository becomes Ready
Test Environment:
- Flux v2.5.1
- source-controller image:
fluxcd/source-controller:v1.5.0 - go-git version: v5.13.2
Root Cause: CVE-2025-21613 Security Fix
The regression is caused by PR #1141 in go-git, which was a security fix for CVE-2025-21613.
Commit: 9550dfab (July 13, 2024)
Title: "plumbing: transport/file, Change paths to absolute"
The Breaking Change
In plumbing/transport/common.go, the parseFile function was modified:
// Before (v5.12.0)
func parseFile(endpoint string) (*Endpoint, bool) {
if giturl.MatchesScheme(endpoint) {
return nil, false
}
path := endpoint // ← Used as-is
return &Endpoint{
Protocol: "file",
Path: path,
}, true
}
// After (v5.13.0+)
func parseFile(endpoint string) (*Endpoint, bool) {
if giturl.MatchesScheme(endpoint) {
return nil, false
}
path, err := filepath.Abs(endpoint) // ← Forced to absolute path
if err != nil {
return nil, false
}
return &Endpoint{
Protocol: "file",
Path: path,
}, true
}
Impact on Submodule Relative URLs
Before the fix:
- Relative URL
../other-repo→file://../other-repo - Processed as relative path
After the fix:
- Relative URL
../other-repo→file:///absolute/path/other-repo - Forced to absolute path, treated as local file
- Triggers file transport which requires git binary
- Results in:
exec: "git": executable file not found in $PATH
Test Evidence
The commit also updated test expectations, proving the behavioral change:
// Before
c.Assert(e.Path, Equals, "foo.git")
c.Assert(e.String(), Equals, "file://foo.git")
// After
abs, err := filepath.Abs("foo.git")
c.Assert(e.Path, Equals, abs)
c.Assert(e.String(), Equals, "file://"+abs)
Impact Assessment
This is an unintended breaking change introduced by a security fix. While the security fix was necessary, it broke the relative URL resolution for submodules that don't use explicit protocols.
Temporary Workaround
Until this regression is fixed:
- Convert relative submodule URLs to absolute URLs in
.gitmodules - Or downgrade to Flux 2.4.0
Hope this helps! Let me know if you need any clarification or want me to test anything else 👍
@cappyzawa thanks for weighing in, that's spot on.
There is already an issue for this in https://github.com/go-git/go-git/issues/1380, which is likely only going to be resolved on the v6 release.
Instead of downgrading to Flux 2.4.0 for which support will end in Q3 2025, I suggest updating the repo with absolute URLs for the Git submodule.
@stefanprodan Just wanted to point that it's not universal solution. For some users this might be just using http vs ssh for cloning, but in our case we have to use different url when cloning from local machine vs from the cluster and using relative path in submodule is the only way this works in all cases.
If we could have some workaround on flux side that would be very appreciated.
If we could have some workaround on flux side that would be very appreciated.
There is no workaround, Flux uses go-git and we can't rollback to a version with CVEs.
Instead of Git submodules you could use the include feature https://fluxcd.io/flux/components/source/gitrepositories/#include
Another option is to replace the GitRepository with an OCIRepository and in CI you'll push the manifests to a container registry with flux push artifact (this is the recommend way IMO).
@stefanprodan what do you think about this workaround suggestion? https://github.com/go-git/go-git/issues/1380#issuecomment-2979539547 Is this viable way to handle it before it's passed to go-git?
Instead of Git submodules you could the include feature https://fluxcd.io/flux/components/source/gitrepositories/#include
This is something I considered before going submodules route, but unfortunately it's not straightforward to version submodule in that way
This was raised once more in #1837