From 40dd442484e0dcec8ae056372b3319e34ec35012 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Mon, 23 Mar 2026 09:08:59 +0100 Subject: [PATCH] fix: resolve relative paths against CWD when parentDir is empty When agent configs are loaded from OCI/URL/bytes sources, parentDir is empty. Previously, functions like GetAbsolutePaths, makeAbsolute, and ResolveDatabasePath would produce broken relative paths via filepath.Join("", path). Now they fall back to filepath.Abs() which correctly resolves against the current working directory. Added debug logging to help diagnose path resolution issues. Fixes #1655 Assisted-By: docker-agent --- pkg/rag/manager.go | 15 ++++++- pkg/rag/manager_test.go | 33 +++++++++++++++ pkg/rag/strategy/helpers.go | 29 ++++++++++--- pkg/rag/strategy/helpers_test.go | 73 ++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 pkg/rag/manager_test.go create mode 100644 pkg/rag/strategy/helpers_test.go diff --git a/pkg/rag/manager.go b/pkg/rag/manager.go index ddc275bca..17e77675f 100644 --- a/pkg/rag/manager.go +++ b/pkg/rag/manager.go @@ -574,12 +574,25 @@ func (m *Manager) readFile(path string) (string, error) { return string(data), nil } -// GetAbsolutePaths converts doc paths to absolute paths +// GetAbsolutePaths converts doc paths to absolute paths relative to basePath. +// If basePath is empty (e.g. for OCI/URL sources), relative paths are resolved +// against the current working directory. func GetAbsolutePaths(basePath string, docPaths []string) []string { var absPaths []string for _, p := range docPaths { if filepath.IsAbs(p) { absPaths = append(absPaths, p) + continue + } + if basePath == "" { + slog.Debug("Resolving relative path with empty basePath, using working directory", "path", p) + abs, err := filepath.Abs(p) + if err != nil { + slog.Warn("Failed to resolve absolute path, using as-is", "path", p, "error", err) + absPaths = append(absPaths, p) + } else { + absPaths = append(absPaths, abs) + } } else { absPaths = append(absPaths, filepath.Join(basePath, p)) } diff --git a/pkg/rag/manager_test.go b/pkg/rag/manager_test.go new file mode 100644 index 000000000..2ebadd1df --- /dev/null +++ b/pkg/rag/manager_test.go @@ -0,0 +1,33 @@ +package rag + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetAbsolutePaths_WithBasePath(t *testing.T) { + result := GetAbsolutePaths("/base", []string{"relative/file.go", "/absolute/file.go"}) + assert.Equal(t, []string{"/base/relative/file.go", "/absolute/file.go"}, result) +} + +func TestGetAbsolutePaths_EmptyBasePath(t *testing.T) { + // When basePath is empty (OCI/URL sources), relative paths should be + // resolved against the current working directory instead of producing + // broken paths like "relative/file.go". + cwd, err := os.Getwd() + require.NoError(t, err) + + result := GetAbsolutePaths("", []string{"relative/file.go", "/absolute/file.go"}) + + assert.Equal(t, filepath.Join(cwd, "relative", "file.go"), result[0]) + assert.Equal(t, "/absolute/file.go", result[1]) +} + +func TestGetAbsolutePaths_NilInput(t *testing.T) { + result := GetAbsolutePaths("/base", nil) + assert.Nil(t, result) +} diff --git a/pkg/rag/strategy/helpers.go b/pkg/rag/strategy/helpers.go index ac34744a4..8b14f1632 100644 --- a/pkg/rag/strategy/helpers.go +++ b/pkg/rag/strategy/helpers.go @@ -60,6 +60,14 @@ func ResolveDatabasePath(dbCfg latest.RAGDatabaseConfig, parentDir, defaultName // If it's a relative file path, make it absolute if !filepath.IsAbs(dbStr) { + if parentDir == "" { + slog.Debug("Resolving relative database path with empty parentDir, using working directory", "path", dbStr) + abs, err := filepath.Abs(dbStr) + if err != nil { + return "", fmt.Errorf("failed to resolve absolute path for %q: %w", dbStr, err) + } + return abs, nil + } return filepath.Join(parentDir, dbStr), nil } @@ -165,12 +173,23 @@ func GetParamPtr[T any](params map[string]any, key string) *T { } } -// makeAbsolute makes a path absolute relative to parentDir -func makeAbsolute(path, parentDir string) string { - if filepath.IsAbs(path) { - return path +// makeAbsolute makes a path absolute relative to parentDir. +// If parentDir is empty (e.g. for OCI/URL sources), the path is resolved +// against the current working directory. +func makeAbsolute(p, parentDir string) string { + if filepath.IsAbs(p) { + return p + } + if parentDir == "" { + slog.Debug("Resolving relative path with empty parentDir, using working directory", "path", p) + abs, err := filepath.Abs(p) + if err != nil { + slog.Warn("Failed to resolve absolute path, using as-is", "path", p, "error", err) + return p + } + return abs } - return filepath.Join(parentDir, path) + return filepath.Join(parentDir, p) } // EmitEvent sends an event to the events channel using non-blocking send diff --git a/pkg/rag/strategy/helpers_test.go b/pkg/rag/strategy/helpers_test.go new file mode 100644 index 000000000..019e1e858 --- /dev/null +++ b/pkg/rag/strategy/helpers_test.go @@ -0,0 +1,73 @@ +package strategy + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/docker/docker-agent/pkg/config/latest" +) + +// newDBConfig creates a RAGDatabaseConfig for testing via YAML unmarshaling. +func newDBConfig(t *testing.T, value string) latest.RAGDatabaseConfig { + t.Helper() + var cfg latest.RAGDatabaseConfig + err := cfg.UnmarshalYAML(func(v any) error { + p, ok := v.(*string) + if !ok { + return nil + } + *p = value + return nil + }) + require.NoError(t, err) + return cfg +} + +func TestMakeAbsolute_WithParentDir(t *testing.T) { + assert.Equal(t, "/parent/relative.go", makeAbsolute("relative.go", "/parent")) + assert.Equal(t, "/absolute/file.go", makeAbsolute("/absolute/file.go", "/parent")) +} + +func TestMakeAbsolute_EmptyParentDir(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + + result := makeAbsolute("relative.go", "") + assert.Equal(t, filepath.Join(cwd, "relative.go"), result) +} + +func TestResolveDatabasePath_EmptyParentDir(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + + result, err := ResolveDatabasePath(newDBConfig(t, "./my.db"), "", "default") + require.NoError(t, err) + assert.Equal(t, filepath.Join(cwd, "my.db"), result) +} + +func TestResolveDatabasePath_AbsolutePathIgnoresParentDir(t *testing.T) { + result, err := ResolveDatabasePath(newDBConfig(t, "/absolute/my.db"), "/parent", "default") + require.NoError(t, err) + assert.Equal(t, "/absolute/my.db", result) +} + +func TestResolveDatabasePath_RelativeWithParentDir(t *testing.T) { + result, err := ResolveDatabasePath(newDBConfig(t, "./my.db"), "/parent", "default") + require.NoError(t, err) + assert.Equal(t, "/parent/my.db", result) +} + +func TestMergeDocPaths_EmptyParentDir(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + + result := MergeDocPaths([]string{"shared.go"}, []string{"extra.go"}, "") + assert.Equal(t, []string{ + filepath.Join(cwd, "shared.go"), + filepath.Join(cwd, "extra.go"), + }, result) +}