From 8e0337499597b39523f8c2ddfc5aa7d414dc4559 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Wed, 22 Jul 2026 23:52:38 +0530 Subject: [PATCH 1/7] fix(filesystem): support path-scoped glob patterns in post-edit hooks --- pkg/tools/builtin/filesystem/postedit.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/tools/builtin/filesystem/postedit.go b/pkg/tools/builtin/filesystem/postedit.go index 52935ec9fe..23323684ef 100644 --- a/pkg/tools/builtin/filesystem/postedit.go +++ b/pkg/tools/builtin/filesystem/postedit.go @@ -8,6 +8,7 @@ import ( "log/slog" "os/exec" "path/filepath" + "strings" "github.com/docker/docker-agent/pkg/shellpath" ) @@ -15,7 +16,12 @@ import ( // runPostEditCommands executes configured shell commands after a file edit. func runPostEditCommands(ctx context.Context, postEditCommands []PostEditConfig, filePath string) error { for _, postEdit := range postEditCommands { - matched, err := filepath.Match(postEdit.Path, filepath.Base(filePath)) + pattern := filepath.ToSlash(postEdit.Path) + target := filepath.Base(filePath) + if strings.Contains(pattern, "/") { + target = filepath.ToSlash(filePath) + } + matched, err := filepath.Match(pattern, target) if err != nil { slog.WarnContext(ctx, "Invalid post-edit pattern", "pattern", postEdit.Path, "error", err) continue From 4074d7ca2a8396e15fdf55e66667a466ace61a92 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Wed, 22 Jul 2026 23:52:44 +0530 Subject: [PATCH 2/7] fix(tools): fix POSIX path resolution and test assertions on Windows --- pkg/session/session.go | 2 +- pkg/tools/workingdir/workingdir.go | 4 ---- pkg/tui/internal/editorname/editorname_test.go | 9 ++++++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/session/session.go b/pkg/session/session.go index dece41c121..dcc239e27c 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -1202,7 +1202,7 @@ func (s *Session) AddAttachedFile(absPath string) { if absPath == "" { return } - if !filepath.IsAbs(absPath) { + if !filepath.IsAbs(absPath) && !strings.HasPrefix(absPath, "/") { slog.Debug("ignoring non-absolute attached file path", "session_id", s.ID, "path", absPath) return } diff --git a/pkg/tools/workingdir/workingdir.go b/pkg/tools/workingdir/workingdir.go index ee8209269a..322aad1f19 100644 --- a/pkg/tools/workingdir/workingdir.go +++ b/pkg/tools/workingdir/workingdir.go @@ -18,10 +18,6 @@ func Resolve(toolsetWorkingDir, agentWorkingDir string) string { return filepath.Clean(toolsetWorkingDir) } if agentWorkingDir != "" { - abs, err := filepath.Abs(filepath.Join(agentWorkingDir, toolsetWorkingDir)) - if err == nil { - return abs - } return filepath.Join(agentWorkingDir, toolsetWorkingDir) } return toolsetWorkingDir diff --git a/pkg/tui/internal/editorname/editorname_test.go b/pkg/tui/internal/editorname/editorname_test.go index 95a9533198..29d34b5467 100644 --- a/pkg/tui/internal/editorname/editorname_test.go +++ b/pkg/tui/internal/editorname/editorname_test.go @@ -9,6 +9,13 @@ import ( "github.com/stretchr/testify/assert" ) +func defaultEditor() string { + if goruntime.GOOS == "windows" { + return "Notepad" + } + return "Vi" +} + func TestFromEnv(t *testing.T) { t.Parallel() @@ -70,7 +77,7 @@ func TestFromEnv(t *testing.T) { name: "Empty (uses platform default)", visual: "", editorEnv: "", - want: map[bool]string{true: "Notepad", false: "Vi"}[goruntime.GOOS == "windows"], + want: defaultEditor(), }, { name: "VSCode Insiders", From 750665cece18d4a7d844773b400383d11c8bec3b Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Thu, 23 Jul 2026 11:53:54 +0530 Subject: [PATCH 3/7] fix(windows): resolve cross-platform path handling and linting errors --- pkg/selfupdate/exec_windows.go | 6 +++--- pkg/tools/builtin/filesystem/filesystem.go | 2 +- pkg/tools/builtin/filesystem/postedit.go | 10 ++++++++-- pkg/tools/builtin/filesystem/postedit_js.go | 2 +- pkg/tools/workingdir/workingdir.go | 4 ++++ 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pkg/selfupdate/exec_windows.go b/pkg/selfupdate/exec_windows.go index 6517efcc91..27c599be10 100644 --- a/pkg/selfupdate/exec_windows.go +++ b/pkg/selfupdate/exec_windows.go @@ -27,9 +27,9 @@ func swapBinary(dst, src string) error { if cpErr := atomicWriteFromFile(dst, src); cpErr != nil { // Roll back so we never leave the install without a binary. if rbErr := os.Rename(old, dst); rbErr != nil { - return fmt.Errorf("installing new binary: %w (copy fallback failed: %v; rollback also failed: %v)", err, cpErr, rbErr) + return fmt.Errorf("installing new binary: %w (copy fallback failed: %v; rollback also failed: %v)", err, cpErr, rbErr) //nolint:errorlint // can only wrap one error } - return fmt.Errorf("installing new binary: %w (copy fallback failed: %v)", err, cpErr) + return fmt.Errorf("installing new binary: %w (copy fallback failed: %v)", err, cpErr) //nolint:errorlint // can only wrap one error } _ = os.Remove(src) } @@ -48,7 +48,7 @@ func reExecProcess(path string, args, env []string) error { childArgs = args[1:] } - cmd := exec.Command(path, childArgs...) //nolint:gosec // path is our own freshly installed binary + cmd := exec.Command(path, childArgs...) //nolint:noctx // context not applicable here cmd.Env = env cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout diff --git a/pkg/tools/builtin/filesystem/filesystem.go b/pkg/tools/builtin/filesystem/filesystem.go index ad28b2d20f..8e90aee689 100644 --- a/pkg/tools/builtin/filesystem/filesystem.go +++ b/pkg/tools/builtin/filesystem/filesystem.go @@ -589,7 +589,7 @@ func (t *ToolSet) executePostEditCommands(ctx context.Context, filePath string) if len(t.postEditCommands) == 0 { return nil } - return runPostEditCommands(ctx, t.postEditCommands, filePath) + return runPostEditCommands(ctx, t.workingDir, t.postEditCommands, filePath) } // resolvePath resolves a path relative to the working directory. diff --git a/pkg/tools/builtin/filesystem/postedit.go b/pkg/tools/builtin/filesystem/postedit.go index 23323684ef..392f951f10 100644 --- a/pkg/tools/builtin/filesystem/postedit.go +++ b/pkg/tools/builtin/filesystem/postedit.go @@ -14,12 +14,18 @@ import ( ) // runPostEditCommands executes configured shell commands after a file edit. -func runPostEditCommands(ctx context.Context, postEditCommands []PostEditConfig, filePath string) error { +func runPostEditCommands(ctx context.Context, workingDir string, postEditCommands []PostEditConfig, filePath string) error { for _, postEdit := range postEditCommands { pattern := filepath.ToSlash(postEdit.Path) target := filepath.Base(filePath) if strings.Contains(pattern, "/") { - target = filepath.ToSlash(filePath) + // Make filePath relative to workingDir so "pkg/*.go" can match + rel, err := filepath.Rel(workingDir, filePath) + if err == nil { + target = filepath.ToSlash(rel) + } else { + target = filepath.ToSlash(filePath) + } } matched, err := filepath.Match(pattern, target) if err != nil { diff --git a/pkg/tools/builtin/filesystem/postedit_js.go b/pkg/tools/builtin/filesystem/postedit_js.go index 057aa52522..ece867fc6b 100644 --- a/pkg/tools/builtin/filesystem/postedit_js.go +++ b/pkg/tools/builtin/filesystem/postedit_js.go @@ -5,6 +5,6 @@ package filesystem import "context" // runPostEditCommands is a no-op under js/wasm (no os/exec available). -func runPostEditCommands(_ context.Context, _ []PostEditConfig, _ string) error { +func runPostEditCommands(_ context.Context, _ string, _ []PostEditConfig, _ string) error { return nil } diff --git a/pkg/tools/workingdir/workingdir.go b/pkg/tools/workingdir/workingdir.go index 322aad1f19..ee8209269a 100644 --- a/pkg/tools/workingdir/workingdir.go +++ b/pkg/tools/workingdir/workingdir.go @@ -18,6 +18,10 @@ func Resolve(toolsetWorkingDir, agentWorkingDir string) string { return filepath.Clean(toolsetWorkingDir) } if agentWorkingDir != "" { + abs, err := filepath.Abs(filepath.Join(agentWorkingDir, toolsetWorkingDir)) + if err == nil { + return abs + } return filepath.Join(agentWorkingDir, toolsetWorkingDir) } return toolsetWorkingDir From 8b300a618c7d3513c37f86b0fb60caa1b34a75f9 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Thu, 23 Jul 2026 11:57:45 +0530 Subject: [PATCH 4/7] fix(windows): resolve remaining gosec linting errors --- pkg/tools/builtin/backgroundjobs/cmd_windows.go | 4 ++-- pkg/tools/builtin/shell/cmd_windows.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/tools/builtin/backgroundjobs/cmd_windows.go b/pkg/tools/builtin/backgroundjobs/cmd_windows.go index d25a83ef15..5a4ce50c2b 100644 --- a/pkg/tools/builtin/backgroundjobs/cmd_windows.go +++ b/pkg/tools/builtin/backgroundjobs/cmd_windows.go @@ -31,13 +31,13 @@ func createProcessGroup(proc *os.Process) (*processGroup, error) { if _, err := windows.SetInformationJobObject( job, windows.JobObjectExtendedLimitInformation, - uintptr(unsafe.Pointer(&info)), + uintptr(unsafe.Pointer(&info)), //nolint:gosec // interacting with Windows API uint32(unsafe.Sizeof(info))); err != nil { _ = windows.CloseHandle(job) return nil, err } - handle, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(proc.Pid)) + handle, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(proc.Pid)) //nolint:gosec // PID fits in uint32 on Windows if err != nil { _ = windows.CloseHandle(job) return nil, err diff --git a/pkg/tools/builtin/shell/cmd_windows.go b/pkg/tools/builtin/shell/cmd_windows.go index 05e11368ac..9c2946376d 100644 --- a/pkg/tools/builtin/shell/cmd_windows.go +++ b/pkg/tools/builtin/shell/cmd_windows.go @@ -31,13 +31,13 @@ func createProcessGroup(proc *os.Process) (*processGroup, error) { if _, err := windows.SetInformationJobObject( job, windows.JobObjectExtendedLimitInformation, - uintptr(unsafe.Pointer(&info)), + uintptr(unsafe.Pointer(&info)), //nolint:gosec // interacting with Windows API uint32(unsafe.Sizeof(info))); err != nil { _ = windows.CloseHandle(job) return nil, err } - handle, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(proc.Pid)) + handle, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(proc.Pid)) //nolint:gosec // PID fits in uint32 on Windows if err != nil { _ = windows.CloseHandle(job) return nil, err From 8f4124b4d874abb8046c8f3dca8416d52b819c86 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Fri, 24 Jul 2026 20:10:36 +0530 Subject: [PATCH 5/7] fix: address PR review comments for Windows compatibility --- pkg/selfupdate/exec_windows.go | 6 +- pkg/session/session.go | 2 +- pkg/session/session_options_test.go | 37 ++++++-- .../builtin/filesystem/agentsignore_test.go | 2 + .../filesystem/filesystem_paths_test.go | 2 + pkg/tools/builtin/filesystem/postedit.go | 42 ++++---- pkg/tools/builtin/filesystem/postedit_test.go | 95 +++++++++++++++++++ pkg/tools/workingdir/workingdir_test.go | 3 + 8 files changed, 158 insertions(+), 31 deletions(-) create mode 100644 pkg/tools/builtin/filesystem/postedit_test.go diff --git a/pkg/selfupdate/exec_windows.go b/pkg/selfupdate/exec_windows.go index 27c599be10..d00dd09e5c 100644 --- a/pkg/selfupdate/exec_windows.go +++ b/pkg/selfupdate/exec_windows.go @@ -27,9 +27,9 @@ func swapBinary(dst, src string) error { if cpErr := atomicWriteFromFile(dst, src); cpErr != nil { // Roll back so we never leave the install without a binary. if rbErr := os.Rename(old, dst); rbErr != nil { - return fmt.Errorf("installing new binary: %w (copy fallback failed: %v; rollback also failed: %v)", err, cpErr, rbErr) //nolint:errorlint // can only wrap one error + return fmt.Errorf("installing new binary: %w (copy fallback failed: %w; rollback also failed: %w)", err, cpErr, rbErr) } - return fmt.Errorf("installing new binary: %w (copy fallback failed: %v)", err, cpErr) //nolint:errorlint // can only wrap one error + return fmt.Errorf("installing new binary: %w (copy fallback failed: %w)", err, cpErr) } _ = os.Remove(src) } @@ -48,7 +48,7 @@ func reExecProcess(path string, args, env []string) error { childArgs = args[1:] } - cmd := exec.Command(path, childArgs...) //nolint:noctx // context not applicable here + cmd := exec.Command(path, childArgs...) //nolint:noctx // re-exec has no parent context cmd.Env = env cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout diff --git a/pkg/session/session.go b/pkg/session/session.go index dcc239e27c..dece41c121 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -1202,7 +1202,7 @@ func (s *Session) AddAttachedFile(absPath string) { if absPath == "" { return } - if !filepath.IsAbs(absPath) && !strings.HasPrefix(absPath, "/") { + if !filepath.IsAbs(absPath) { slog.Debug("ignoring non-absolute attached file path", "session_id", s.ID, "path", absPath) return } diff --git a/pkg/session/session_options_test.go b/pkg/session/session_options_test.go index 5d0c4108f7..89791b6dab 100644 --- a/pkg/session/session_options_test.go +++ b/pkg/session/session_options_test.go @@ -76,10 +76,13 @@ func TestNewSession_ConsistencyBetweenInitialAndSpawned(t *testing.T) { func TestAddAttachedFile(t *testing.T) { t.Parallel() - foo := filepath.Join(t.TempDir(), "abs", "foo.go") - bar := filepath.Join(t.TempDir(), "abs", "bar.go") + t.Run("deduplicates and preserves order", func(t *testing.T) { t.Parallel() + dir := t.TempDir() + foo := filepath.Join(dir, "foo.go") + bar := filepath.Join(dir, "bar.go") + s := New() s.AddAttachedFile(foo) s.AddAttachedFile(bar) @@ -105,6 +108,9 @@ func TestAddAttachedFile(t *testing.T) { t.Run("snapshot is independent of session storage", func(t *testing.T) { t.Parallel() + dir := t.TempDir() + foo := filepath.Join(dir, "foo.go") + s := New() s.AddAttachedFile(foo) snap := s.AttachedFilesSnapshot() @@ -116,24 +122,28 @@ func TestAddAttachedFile(t *testing.T) { func TestRemoveAttachedFile(t *testing.T) { t.Parallel() - root := t.TempDir() - foo := filepath.Join(root, "foo.go") - bar := filepath.Join(root, "bar.go") - baz := filepath.Join(root, "baz.go") - other := filepath.Join(root, "other.go") + t.Run("removes and reports presence", func(t *testing.T) { t.Parallel() + dir := t.TempDir() + foo := filepath.Join(dir, "foo.go") + bar := filepath.Join(dir, "bar.go") + baz := filepath.Join(dir, "baz.go") + s := New() s.AddAttachedFile(foo) s.AddAttachedFile(bar) s.AddAttachedFile(baz) - assert.True(t, s.RemoveAttachedFile(bar)) assert.Equal(t, []string{foo, baz}, s.AttachedFilesSnapshot()) }) t.Run("reports absent paths", func(t *testing.T) { t.Parallel() + dir := t.TempDir() + foo := filepath.Join(dir, "foo.go") + other := filepath.Join(dir, "other.go") + s := New() s.AddAttachedFile(foo) assert.False(t, s.RemoveAttachedFile(other)) @@ -143,6 +153,9 @@ func TestRemoveAttachedFile(t *testing.T) { t.Run("no-op on empty list", func(t *testing.T) { t.Parallel() + dir := t.TempDir() + foo := filepath.Join(dir, "foo.go") + s := New() assert.False(t, s.RemoveAttachedFile(foo)) assert.Empty(t, s.AttachedFilesSnapshot()) @@ -150,6 +163,9 @@ func TestRemoveAttachedFile(t *testing.T) { t.Run("file can be re-attached after removal", func(t *testing.T) { t.Parallel() + dir := t.TempDir() + foo := filepath.Join(dir, "foo.go") + s := New() s.AddAttachedFile(foo) require.True(t, s.RemoveAttachedFile(foo)) @@ -160,8 +176,9 @@ func TestRemoveAttachedFile(t *testing.T) { func TestWithAttachedFiles(t *testing.T) { t.Parallel() - foo := filepath.Join(t.TempDir(), "abs", "foo.go") - bar := filepath.Join(t.TempDir(), "abs", "bar.go") + dir := t.TempDir() + foo := filepath.Join(dir, "foo.go") + bar := filepath.Join(dir, "bar.go") s := New(WithAttachedFiles([]string{foo, "", "relative/path.go", bar, foo})) assert.Equal(t, []string{foo, bar}, s.AttachedFilesSnapshot()) } diff --git a/pkg/tools/builtin/filesystem/agentsignore_test.go b/pkg/tools/builtin/filesystem/agentsignore_test.go index 54df72db84..a3c3f36726 100644 --- a/pkg/tools/builtin/filesystem/agentsignore_test.go +++ b/pkg/tools/builtin/filesystem/agentsignore_test.go @@ -3,6 +3,7 @@ package filesystem import ( "os" "path/filepath" + "testing" "github.com/stretchr/testify/assert" @@ -135,6 +136,7 @@ func TestAgentsIgnoreNegationReIncludes(t *testing.T) { } func TestAgentsIgnoreUnreadableFileIsAnError(t *testing.T) { + dir := t.TempDir() path := filepath.Join(dir, fsx.AgentsIgnoreFile) require.NoError(t, os.WriteFile(path, []byte("secrets.env\n"), 0o644)) diff --git a/pkg/tools/builtin/filesystem/filesystem_paths_test.go b/pkg/tools/builtin/filesystem/filesystem_paths_test.go index ad68ef368c..4d50d401ee 100644 --- a/pkg/tools/builtin/filesystem/filesystem_paths_test.go +++ b/pkg/tools/builtin/filesystem/filesystem_paths_test.go @@ -3,6 +3,7 @@ package filesystem import ( "os" "path/filepath" + "testing" "github.com/stretchr/testify/assert" @@ -174,6 +175,7 @@ func TestFilesystemTool_DenyList_TakesPrecedenceOverAllowList(t *testing.T) { tool := newTestToolSet(t, wd, WithAllowList([]string{"."}), WithDenyList([]string{"src/vendor"})) + defer tool.Close() // Allowed by allow-list, not denied. _, err := tool.resolveAndCheckPath("src/main.go") diff --git a/pkg/tools/builtin/filesystem/postedit.go b/pkg/tools/builtin/filesystem/postedit.go index 392f951f10..5d78e2f8ec 100644 --- a/pkg/tools/builtin/filesystem/postedit.go +++ b/pkg/tools/builtin/filesystem/postedit.go @@ -16,23 +16,7 @@ import ( // runPostEditCommands executes configured shell commands after a file edit. func runPostEditCommands(ctx context.Context, workingDir string, postEditCommands []PostEditConfig, filePath string) error { for _, postEdit := range postEditCommands { - pattern := filepath.ToSlash(postEdit.Path) - target := filepath.Base(filePath) - if strings.Contains(pattern, "/") { - // Make filePath relative to workingDir so "pkg/*.go" can match - rel, err := filepath.Rel(workingDir, filePath) - if err == nil { - target = filepath.ToSlash(rel) - } else { - target = filepath.ToSlash(filePath) - } - } - matched, err := filepath.Match(pattern, target) - if err != nil { - slog.WarnContext(ctx, "Invalid post-edit pattern", "pattern", postEdit.Path, "error", err) - continue - } - if !matched { + if !matchPostEdit(ctx, postEdit.Path, workingDir, filePath) { continue } @@ -47,3 +31,27 @@ func runPostEditCommands(ctx context.Context, workingDir string, postEditCommand } return nil } + +func matchPostEdit(ctx context.Context, patternStr, workingDir, filePath string) bool { + pattern := filepath.ToSlash(patternStr) + target := filepath.Base(filePath) + if strings.Contains(pattern, "/") { + if workingDir != "" { + rel, err := filepath.Rel(workingDir, filePath) + if err == nil { + target = filepath.ToSlash(rel) + } else { + slog.DebugContext(ctx, "Failed to resolve relative path for post-edit pattern", "workingDir", workingDir, "filePath", filePath, "error", err) + target = filepath.ToSlash(filePath) + } + } else { + target = filepath.ToSlash(filePath) + } + } + matched, err := filepath.Match(pattern, target) + if err != nil { + slog.WarnContext(ctx, "Invalid post-edit pattern", "pattern", patternStr, "error", err) + return false + } + return matched +} diff --git a/pkg/tools/builtin/filesystem/postedit_test.go b/pkg/tools/builtin/filesystem/postedit_test.go new file mode 100644 index 0000000000..6159ef1aae --- /dev/null +++ b/pkg/tools/builtin/filesystem/postedit_test.go @@ -0,0 +1,95 @@ +//go:build !js + +package filesystem + +import ( + "context" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMatchPostEdit(t *testing.T) { + ctx := context.Background() + workDir := filepath.Join(string(filepath.Separator), "workspace", "app") + + tests := []struct { + name string + pattern string + workingDir string + filePath string + wantMatch bool + }{ + { + name: "basename pattern matches simple file", + pattern: "*.go", + workingDir: workDir, + filePath: filepath.Join(workDir, "main.go"), + wantMatch: true, + }, + { + name: "basename pattern matches nested file", + pattern: "*.go", + workingDir: workDir, + filePath: filepath.Join(workDir, "pkg", "sub", "foo.go"), + wantMatch: true, + }, + { + name: "path-scoped pattern matches relative subpath", + pattern: "pkg/*.go", + workingDir: workDir, + filePath: filepath.Join(workDir, "pkg", "foo.go"), + wantMatch: true, + }, + { + name: "path-scoped pattern does not match different subpath", + pattern: "cmd/*.go", + workingDir: workDir, + filePath: filepath.Join(workDir, "pkg", "foo.go"), + wantMatch: false, + }, + { + name: "nested slash pattern matches multi-level path", + pattern: "pkg/sub/*.go", + workingDir: workDir, + filePath: filepath.Join(workDir, "pkg", "sub", "bar.go"), + wantMatch: true, + }, + { + name: "empty working dir falls back to slash-normalized file path", + pattern: "*.go", + workingDir: "", + filePath: filepath.Join("pkg", "foo.go"), + wantMatch: true, + }, + { + name: "invalid pattern returns false", + pattern: "[invalid", + workingDir: workDir, + filePath: filepath.Join(workDir, "foo.go"), + wantMatch: false, + }, + { + name: "file outside workingDir does not match path-scoped pattern", + pattern: "pkg/*.go", + workingDir: workDir, + filePath: filepath.Join(workDir, "..", "outside", "foo.go"), + wantMatch: false, + }, + { + name: "relative workingDir vs absolute filePath debug log fallback does not match relative pattern", + pattern: "pkg/*.go", + workingDir: "relative/dir", + filePath: filepath.Join(workDir, "pkg", "foo.go"), + wantMatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := matchPostEdit(ctx, tt.pattern, tt.workingDir, tt.filePath) + assert.Equal(t, tt.wantMatch, got) + }) + } +} diff --git a/pkg/tools/workingdir/workingdir_test.go b/pkg/tools/workingdir/workingdir_test.go index 72309754de..4fa0a274ad 100644 --- a/pkg/tools/workingdir/workingdir_test.go +++ b/pkg/tools/workingdir/workingdir_test.go @@ -7,12 +7,15 @@ import ( "github.com/stretchr/testify/assert" ) + func TestResolve(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) workspace := t.TempDir() absolute := filepath.Join(t.TempDir(), "app") + + tests := []struct { name string toolsetWorkingDir string From 341f188bb36726970b9b6333ef763e15f33d4299 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Fri, 24 Jul 2026 20:35:10 +0530 Subject: [PATCH 6/7] test: use t.Context() to fix forbidigo lint error --- pkg/tools/builtin/filesystem/postedit_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/tools/builtin/filesystem/postedit_test.go b/pkg/tools/builtin/filesystem/postedit_test.go index 6159ef1aae..7a16e8cfe1 100644 --- a/pkg/tools/builtin/filesystem/postedit_test.go +++ b/pkg/tools/builtin/filesystem/postedit_test.go @@ -3,7 +3,6 @@ package filesystem import ( - "context" "path/filepath" "testing" @@ -11,7 +10,7 @@ import ( ) func TestMatchPostEdit(t *testing.T) { - ctx := context.Background() + ctx := t.Context() workDir := filepath.Join(string(filepath.Separator), "workspace", "app") tests := []struct { From fe7584a720d4fbd864cf0b07db5ab07345319803 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Mon, 3 Aug 2026 22:45:35 +0530 Subject: [PATCH 7/7] fix(lint): resolve remaining formatting and staticcheck errors --- pkg/model/provider/gemini/schema_boolean_test.go | 10 ++++------ pkg/tools/builtin/filesystem/agentsignore_test.go | 2 -- pkg/tools/builtin/filesystem/filesystem_paths_test.go | 1 - pkg/tools/workingdir/workingdir_test.go | 3 --- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/pkg/model/provider/gemini/schema_boolean_test.go b/pkg/model/provider/gemini/schema_boolean_test.go index d1875d5949..f18e2c3642 100644 --- a/pkg/model/provider/gemini/schema_boolean_test.go +++ b/pkg/model/provider/gemini/schema_boolean_test.go @@ -2,6 +2,8 @@ package gemini import ( "testing" + + "github.com/stretchr/testify/require" ) // A tool input schema containing a boolean sub-schema — the shape a JSON Schema @@ -30,12 +32,8 @@ func TestConvertParametersToSchema_BooleanSubSchema(t *testing.T) { } schema, err := ConvertParametersToSchema(params) - if err != nil { - t.Fatalf("ConvertParametersToSchema: %v", err) - } - if schema == nil { - t.Fatal("nil schema") - } + require.NoError(t, err, "ConvertParametersToSchema failed") + require.NotNil(t, schema, "nil schema") if _, ok := schema.Properties["count"]; !ok { t.Errorf("count property dropped; got %v", schema.Properties) } diff --git a/pkg/tools/builtin/filesystem/agentsignore_test.go b/pkg/tools/builtin/filesystem/agentsignore_test.go index a3c3f36726..54df72db84 100644 --- a/pkg/tools/builtin/filesystem/agentsignore_test.go +++ b/pkg/tools/builtin/filesystem/agentsignore_test.go @@ -3,7 +3,6 @@ package filesystem import ( "os" "path/filepath" - "testing" "github.com/stretchr/testify/assert" @@ -136,7 +135,6 @@ func TestAgentsIgnoreNegationReIncludes(t *testing.T) { } func TestAgentsIgnoreUnreadableFileIsAnError(t *testing.T) { - dir := t.TempDir() path := filepath.Join(dir, fsx.AgentsIgnoreFile) require.NoError(t, os.WriteFile(path, []byte("secrets.env\n"), 0o644)) diff --git a/pkg/tools/builtin/filesystem/filesystem_paths_test.go b/pkg/tools/builtin/filesystem/filesystem_paths_test.go index 4d50d401ee..1f511f9cdd 100644 --- a/pkg/tools/builtin/filesystem/filesystem_paths_test.go +++ b/pkg/tools/builtin/filesystem/filesystem_paths_test.go @@ -3,7 +3,6 @@ package filesystem import ( "os" "path/filepath" - "testing" "github.com/stretchr/testify/assert" diff --git a/pkg/tools/workingdir/workingdir_test.go b/pkg/tools/workingdir/workingdir_test.go index 4fa0a274ad..72309754de 100644 --- a/pkg/tools/workingdir/workingdir_test.go +++ b/pkg/tools/workingdir/workingdir_test.go @@ -7,15 +7,12 @@ import ( "github.com/stretchr/testify/assert" ) - func TestResolve(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) workspace := t.TempDir() absolute := filepath.Join(t.TempDir(), "app") - - tests := []struct { name string toolsetWorkingDir string