From 0a89a254a6ec5fa59358b23d773f27d76060fc2d Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Fri, 12 Jun 2026 10:38:27 +0800 Subject: [PATCH 1/6] fix: add pre-deploy validation for Python bundled mode When Python + bundled dependency resolution is selected but no packages are installed in the source directory (no .dist-info dirs found), azd deploy now fails with a clear error message instead of silently uploading an incomplete ZIP that crashes at runtime. Also prints a platform-specific pip install command after azd ai agent init when bundled mode is selected for Python projects. Fixes #8610 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../internal/cmd/init_validate.go | 31 ++++++++++ .../internal/exterrors/codes.go | 5 ++ .../internal/project/service_target_agent.go | 58 ++++++++++++++++++- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go index 311c8027283..db9b9256920 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go @@ -25,6 +25,7 @@ func validatePostInit(srcDir string, codeConfig *agent_yaml.CodeConfiguration) { } validateDotnetRuntimeVsCsproj(srcDir, codeConfig.Runtime) + validateBundledHint(srcDir, codeConfig) } // validateDotnetRuntimeVsCsproj checks whether the selected .NET runtime version is compatible @@ -122,3 +123,33 @@ func extractTargetFrameworkVersion(csprojContent string) int { } return version } + +// validateBundledHint prints guidance when Python bundled mode is selected, +// reminding the user to install dependencies before deploying. +func validateBundledHint(srcDir string, codeConfig *agent_yaml.CodeConfiguration) { + if codeConfig.DependencyResolution == nil || *codeConfig.DependencyResolution != "bundled" { + return + } + + // Only show hint for Python projects + if !strings.HasPrefix(codeConfig.Runtime, "python_") { + return + } + + // Check if requirements.txt exists + reqPath := filepath.Join(srcDir, "requirements.txt") + if _, err := os.Stat(reqPath); err != nil { + return + } + + // Extract Python version from runtime (e.g. "python_3_14" -> "3.14") + pythonVersion := strings.TrimPrefix(codeConfig.Runtime, "python_") + pythonVersion = strings.Replace(pythonVersion, "_", ".", 1) + + fmt.Printf("\n%s Bundled mode selected. Before deploying, install dependencies targeting the deployment platform:\n", + color.YellowString("NOTE:")) + fmt.Printf(" cd %s\n", srcDir) + fmt.Printf(" pip install -r requirements.txt -t . \\\n") + fmt.Printf(" --platform manylinux_2_17_x86_64 --platform linux_x86_64 --platform any \\\n") + fmt.Printf(" --python-version %s --implementation cp --only-binary=:all: --upgrade\n\n", pythonVersion) +} diff --git a/cli/azd/extensions/azure.ai.agents/internal/exterrors/codes.go b/cli/azd/extensions/azure.ai.agents/internal/exterrors/codes.go index 87b7e0c1219..d00d054851f 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/exterrors/codes.go +++ b/cli/azd/extensions/azure.ai.agents/internal/exterrors/codes.go @@ -114,6 +114,11 @@ const ( CodeInvalidFilePath = "invalid_file_path" ) +// Error codes for packaging/deploy errors. +const ( + CodeBundledDepsNotFound = "bundled_deps_not_found" +) + // Error codes for toolbox operations. const ( CodeInvalidToolbox = "invalid_toolbox" diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index aa33e2fdffc..2a18ff1dee5 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -1420,6 +1420,14 @@ func (p *AgentServiceTargetProvider) packageCodeDeploy(ctx context.Context, serv if isDotnet && isBundled { return p.packageDotnetBundled(srcDir) } + + // Python bundled: validate that dependencies are installed in srcDir + isPython := strings.HasPrefix(agentDef.CodeConfiguration.Runtime, "python_") + if isPython && isBundled { + if err := validatePythonBundledDeps(srcDir); err != nil { + return "", "", err + } + } } } @@ -1669,7 +1677,55 @@ func (p *AgentServiceTargetProvider) packageDotnetBundled(srcDir string) (string return tmpPath, sha256Hex, nil } -// deployHostedCodeAgent deploys a code-based hosted agent via multipart ZIP upload. +// validatePythonBundledDeps checks that a Python project in bundled mode has +// installed dependencies in the source directory. It looks for .dist-info +// directories which are always created by pip install --target. +// Only returns an error if requirements.txt exists AND has content AND no +// .dist-info directories are found — this avoids false positives. +func validatePythonBundledDeps(srcDir string) error { + // Check if requirements.txt exists and has non-empty content + reqPath := filepath.Join(srcDir, "requirements.txt") + data, err := os.ReadFile(reqPath) //nolint:gosec // path from internal state + if err != nil { + // No requirements.txt — nothing to validate + return nil + } + + // Check if requirements.txt has any non-comment, non-empty lines + hasRequirements := false + for _, line := range strings.Split(string(data), "\n") { + line = strings.TrimSpace(line) + if line != "" && !strings.HasPrefix(line, "#") { + hasRequirements = true + break + } + } + if !hasRequirements { + return nil + } + + // Look for any *.dist-info directory in srcDir (top-level only) + entries, err := os.ReadDir(srcDir) + if err != nil { + return nil + } + + for _, e := range entries { + if e.IsDir() && strings.HasSuffix(e.Name(), ".dist-info") { + // Found at least one installed package — pass + return nil + } + } + + return exterrors.Dependency( + exterrors.CodeBundledDepsNotFound, + "bundled mode is configured but no installed packages were found in the source directory. "+ + "Dependencies must be installed locally before deploying", + "run: pip install -r requirements.txt -t "+srcDir+ + " --platform manylinux_2_17_x86_64 --platform linux_x86_64 --platform any"+ + " --implementation cp --only-binary=:all:", + ) +} func (p *AgentServiceTargetProvider) deployHostedCodeAgent( ctx context.Context, serviceConfig *azdext.ServiceConfig, From fb5421f3eb4014256abeeba69ba41511ac3ef114 Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Fri, 12 Jun 2026 10:45:10 +0800 Subject: [PATCH 2/6] fix: improve bundled deploy guidance and validation robustness - Make pip install command advisory with fallback note for packages lacking pre-built wheels - Expand dist-info check to one subdirectory level (covers pip install to vendor/ or lib/ patterns) to prevent false positive deploy blocks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../internal/cmd/init_validate.go | 13 +++++++----- .../internal/project/service_target_agent.go | 20 ++++++++++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go index db9b9256920..8f16f46a768 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go @@ -146,10 +146,13 @@ func validateBundledHint(srcDir string, codeConfig *agent_yaml.CodeConfiguration pythonVersion := strings.TrimPrefix(codeConfig.Runtime, "python_") pythonVersion = strings.Replace(pythonVersion, "_", ".", 1) - fmt.Printf("\n%s Bundled mode selected. Before deploying, install dependencies targeting the deployment platform:\n", + fmt.Printf("\n%s Bundled mode selected. Before deploying, install dependencies into the source directory.\n", color.YellowString("NOTE:")) - fmt.Printf(" cd %s\n", srcDir) - fmt.Printf(" pip install -r requirements.txt -t . \\\n") - fmt.Printf(" --platform manylinux_2_17_x86_64 --platform linux_x86_64 --platform any \\\n") - fmt.Printf(" --python-version %s --implementation cp --only-binary=:all: --upgrade\n\n", pythonVersion) + fmt.Printf(" The deployment target is Linux x86_64 with Python %s. Example command:\n\n", pythonVersion) + fmt.Printf(" cd %s\n", srcDir) + fmt.Printf(" pip install -r requirements.txt -t . \\\n") + fmt.Printf(" --platform manylinux_2_17_x86_64 --platform linux_x86_64 --platform any \\\n") + fmt.Printf(" --python-version %s --implementation cp --only-binary=:all: --upgrade\n\n", pythonVersion) + fmt.Printf(" If some packages lack pre-built wheels, you may need to remove --only-binary=:all:\n") + fmt.Printf(" and build on a matching Linux environment instead.\n\n") } diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index 2a18ff1dee5..c8cdef25f6c 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -1704,7 +1704,9 @@ func validatePythonBundledDeps(srcDir string) error { return nil } - // Look for any *.dist-info directory in srcDir (top-level only) + // Look for any *.dist-info directory in srcDir (top-level only, which is + // where pip install --target . places them). Also check one level deep + // for common patterns like vendor/ or lib/. entries, err := os.ReadDir(srcDir) if err != nil { return nil @@ -1717,6 +1719,22 @@ func validatePythonBundledDeps(srcDir string) error { } } + // Check one level of subdirectories for .dist-info (e.g., vendor/, lib/) + for _, e := range entries { + if !e.IsDir() { + continue + } + subEntries, err := os.ReadDir(filepath.Join(srcDir, e.Name())) + if err != nil { + continue + } + for _, se := range subEntries { + if se.IsDir() && strings.HasSuffix(se.Name(), ".dist-info") { + return nil + } + } + } + return exterrors.Dependency( exterrors.CodeBundledDepsNotFound, "bundled mode is configured but no installed packages were found in the source directory. "+ From d960928b8b29f9988f157506e26f23aad794132b Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Fri, 12 Jun 2026 10:47:56 +0800 Subject: [PATCH 3/6] fix: restore doc comment on deployHostedCodeAgent --- .../azure.ai.agents/internal/project/service_target_agent.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index c8cdef25f6c..f1b965b79b7 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -1744,6 +1744,8 @@ func validatePythonBundledDeps(srcDir string) error { " --implementation cp --only-binary=:all:", ) } + +// deployHostedCodeAgent deploys a code-based hosted agent via multipart ZIP upload. func (p *AgentServiceTargetProvider) deployHostedCodeAgent( ctx context.Context, serviceConfig *azdext.ServiceConfig, From f0dd62b9740df5b452d778753fefea0686f0f8f3 Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Fri, 12 Jun 2026 10:59:33 +0800 Subject: [PATCH 4/6] fix: address Copilot review comments - Only ignore os.ErrNotExist for requirements.txt (surface IO errors) - Return error when srcDir is unreadable instead of silently skipping - Quote srcDir in shell commands for paths with spaces - Add unit tests for validateBundledHint (4 tests) - Add unit tests for validatePythonBundledDeps (6 tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../internal/cmd/init_validate.go | 2 +- .../internal/cmd/init_validate_test.go | 98 +++++++++++++++++++ .../internal/project/service_target_agent.go | 19 +++- .../project/service_target_agent_test.go | 55 +++++++++++ 4 files changed, 169 insertions(+), 5 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go index 8f16f46a768..f312e99bb9d 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go @@ -149,7 +149,7 @@ func validateBundledHint(srcDir string, codeConfig *agent_yaml.CodeConfiguration fmt.Printf("\n%s Bundled mode selected. Before deploying, install dependencies into the source directory.\n", color.YellowString("NOTE:")) fmt.Printf(" The deployment target is Linux x86_64 with Python %s. Example command:\n\n", pythonVersion) - fmt.Printf(" cd %s\n", srcDir) + fmt.Printf(" cd \"%s\"\n", srcDir) fmt.Printf(" pip install -r requirements.txt -t . \\\n") fmt.Printf(" --platform manylinux_2_17_x86_64 --platform linux_x86_64 --platform any \\\n") fmt.Printf(" --python-version %s --implementation cp --only-binary=:all: --upgrade\n\n", pythonVersion) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate_test.go index 6b0f0fe3a71..82e09f599ac 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate_test.go @@ -129,3 +129,101 @@ func TestValidatePostInit_PythonSkipsValidation(t *testing.T) { } validatePostInit("/any/path", codeConfig) } + +func TestValidateBundledHint_PythonBundled(t *testing.T) { + dir := t.TempDir() + err := os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) + if err != nil { + t.Fatal(err) + } + + bundled := "bundled" + codeConfig := &agent_yaml.CodeConfiguration{ + Runtime: "python_3_14", + EntryPoint: "main.py", + DependencyResolution: &bundled, + } + + output, _ := captureStdout(t, func() error { + validateBundledHint(dir, codeConfig) + return nil + }) + + if !strings.Contains(output, "NOTE:") { + t.Errorf("expected NOTE in output, got: %s", output) + } + if !strings.Contains(output, "Example command") { + t.Errorf("expected 'Example command' in output, got: %s", output) + } + if !strings.Contains(output, "--python-version 3.14") { + t.Errorf("expected python version 3.14 in output, got: %s", output) + } + if !strings.Contains(output, "--only-binary=:all:") { + t.Errorf("expected --only-binary flag in output, got: %s", output) + } +} + +func TestValidateBundledHint_RemoteBuildSkipped(t *testing.T) { + dir := t.TempDir() + err := os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) + if err != nil { + t.Fatal(err) + } + + remoteBuild := "remote_build" + codeConfig := &agent_yaml.CodeConfiguration{ + Runtime: "python_3_13", + EntryPoint: "main.py", + DependencyResolution: &remoteBuild, + } + + output, _ := captureStdout(t, func() error { + validateBundledHint(dir, codeConfig) + return nil + }) + + if output != "" { + t.Errorf("expected no output for remote_build, got: %s", output) + } +} + +func TestValidateBundledHint_DotnetBundledSkipped(t *testing.T) { + dir := t.TempDir() + + bundled := "bundled" + codeConfig := &agent_yaml.CodeConfiguration{ + Runtime: "dotnet_9", + EntryPoint: "Agent.dll", + DependencyResolution: &bundled, + } + + output, _ := captureStdout(t, func() error { + validateBundledHint(dir, codeConfig) + return nil + }) + + if output != "" { + t.Errorf("expected no output for dotnet bundled, got: %s", output) + } +} + +func TestValidateBundledHint_NoRequirements(t *testing.T) { + dir := t.TempDir() + // No requirements.txt + + bundled := "bundled" + codeConfig := &agent_yaml.CodeConfiguration{ + Runtime: "python_3_13", + EntryPoint: "main.py", + DependencyResolution: &bundled, + } + + output, _ := captureStdout(t, func() error { + validateBundledHint(dir, codeConfig) + return nil + }) + + if output != "" { + t.Errorf("expected no output without requirements.txt, got: %s", output) + } +} diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index f1b965b79b7..20d652bef09 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -1687,8 +1687,15 @@ func validatePythonBundledDeps(srcDir string) error { reqPath := filepath.Join(srcDir, "requirements.txt") data, err := os.ReadFile(reqPath) //nolint:gosec // path from internal state if err != nil { - // No requirements.txt — nothing to validate - return nil + if errors.Is(err, os.ErrNotExist) { + // No requirements.txt — nothing to validate + return nil + } + return exterrors.Dependency( + exterrors.CodeInvalidFilePath, + fmt.Sprintf("failed to read requirements.txt: %s", err), + "check file permissions for "+reqPath, + ) } // Check if requirements.txt has any non-comment, non-empty lines @@ -1709,7 +1716,11 @@ func validatePythonBundledDeps(srcDir string) error { // for common patterns like vendor/ or lib/. entries, err := os.ReadDir(srcDir) if err != nil { - return nil + return exterrors.Dependency( + exterrors.CodeInvalidFilePath, + fmt.Sprintf("failed to read source directory: %s", err), + "check that the source directory exists and is readable: "+srcDir, + ) } for _, e := range entries { @@ -1739,7 +1750,7 @@ func validatePythonBundledDeps(srcDir string) error { exterrors.CodeBundledDepsNotFound, "bundled mode is configured but no installed packages were found in the source directory. "+ "Dependencies must be installed locally before deploying", - "run: pip install -r requirements.txt -t "+srcDir+ + "run: pip install -r requirements.txt -t \""+srcDir+"\""+ " --platform manylinux_2_17_x86_64 --platform linux_x86_64 --platform any"+ " --implementation cp --only-binary=:all:", ) diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go index a30fca62453..9510ce78732 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go @@ -1884,3 +1884,58 @@ func TestFilterServicesByName(t *testing.T) { require.Equal(t, services, filterServicesByName(services, ""), "empty name returns input unchanged (defensive)") } + +func TestValidatePythonBundledDeps_NoRequirements(t *testing.T) { + dir := t.TempDir() + // No requirements.txt — should pass + err := validatePythonBundledDeps(dir) + require.NoError(t, err) +} + +func TestValidatePythonBundledDeps_EmptyRequirements(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("# just a comment\n\n"), 0600) + + err := validatePythonBundledDeps(dir) + require.NoError(t, err) +} + +func TestValidatePythonBundledDeps_NoDepsInstalled(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) + + err := validatePythonBundledDeps(dir) + require.Error(t, err) + require.Contains(t, err.Error(), "no installed packages were found") +} + +func TestValidatePythonBundledDeps_TopLevelDistInfo(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) + os.MkdirAll(filepath.Join(dir, "azure_ai_agents-1.0.dist-info"), 0755) + + err := validatePythonBundledDeps(dir) + require.NoError(t, err) +} + +func TestValidatePythonBundledDeps_SubdirDistInfo(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) + // Installed into vendor/ subdir + os.MkdirAll(filepath.Join(dir, "vendor", "azure_ai_agents-1.0.dist-info"), 0755) + + err := validatePythonBundledDeps(dir) + require.NoError(t, err) +} + +func TestValidatePythonBundledDeps_ErrorCodeCorrect(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("some-package\n"), 0600) + + err := validatePythonBundledDeps(dir) + require.Error(t, err) + + var localErr *azdext.LocalError + require.True(t, errors.As(err, &localErr)) + require.Equal(t, exterrors.CodeBundledDepsNotFound, localErr.Code) +} From c1d1986c9683b36e504eac0a6e40723ee7a1eb09 Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Fri, 12 Jun 2026 11:12:45 +0800 Subject: [PATCH 5/6] fix: resolve CI lint failures - Use strings.SplitSeq (go fix modernization for Go 1.26) - Handle os.WriteFile/MkdirAll errors in tests (gosec G104) - Add 'manylinux' to cspell dictionary Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/.vscode/cspell.yaml | 1 + .../internal/project/service_target_agent.go | 6 +++--- .../internal/project/service_target_agent_test.go | 14 +++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cli/azd/.vscode/cspell.yaml b/cli/azd/.vscode/cspell.yaml index 60b5c7ab853..fb5f0790c66 100644 --- a/cli/azd/.vscode/cspell.yaml +++ b/cli/azd/.vscode/cspell.yaml @@ -45,6 +45,7 @@ words: - opencode - grpcbroker - msiexec + - manylinux - nosec - npx - oneof diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index 20d652bef09..51784fe6c1f 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -1700,9 +1700,9 @@ func validatePythonBundledDeps(srcDir string) error { // Check if requirements.txt has any non-comment, non-empty lines hasRequirements := false - for _, line := range strings.Split(string(data), "\n") { - line = strings.TrimSpace(line) - if line != "" && !strings.HasPrefix(line, "#") { + for line := range strings.SplitSeq(string(data), "\n") { + trimmed := strings.TrimSpace(line) + if trimmed != "" && !strings.HasPrefix(trimmed, "#") { hasRequirements = true break } diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go index 9510ce78732..a5fbd23d8ba 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go @@ -1894,7 +1894,7 @@ func TestValidatePythonBundledDeps_NoRequirements(t *testing.T) { func TestValidatePythonBundledDeps_EmptyRequirements(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("# just a comment\n\n"), 0600) + require.NoError(t, os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("# just a comment\n\n"), 0600)) err := validatePythonBundledDeps(dir) require.NoError(t, err) @@ -1902,7 +1902,7 @@ func TestValidatePythonBundledDeps_EmptyRequirements(t *testing.T) { func TestValidatePythonBundledDeps_NoDepsInstalled(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) + require.NoError(t, os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600)) err := validatePythonBundledDeps(dir) require.Error(t, err) @@ -1911,8 +1911,8 @@ func TestValidatePythonBundledDeps_NoDepsInstalled(t *testing.T) { func TestValidatePythonBundledDeps_TopLevelDistInfo(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) - os.MkdirAll(filepath.Join(dir, "azure_ai_agents-1.0.dist-info"), 0755) + require.NoError(t, os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "azure_ai_agents-1.0.dist-info"), 0755)) err := validatePythonBundledDeps(dir) require.NoError(t, err) @@ -1920,9 +1920,9 @@ func TestValidatePythonBundledDeps_TopLevelDistInfo(t *testing.T) { func TestValidatePythonBundledDeps_SubdirDistInfo(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600) + require.NoError(t, os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600)) // Installed into vendor/ subdir - os.MkdirAll(filepath.Join(dir, "vendor", "azure_ai_agents-1.0.dist-info"), 0755) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "vendor", "azure_ai_agents-1.0.dist-info"), 0755)) err := validatePythonBundledDeps(dir) require.NoError(t, err) @@ -1930,7 +1930,7 @@ func TestValidatePythonBundledDeps_SubdirDistInfo(t *testing.T) { func TestValidatePythonBundledDeps_ErrorCodeCorrect(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("some-package\n"), 0600) + require.NoError(t, os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("some-package\n"), 0600)) err := validatePythonBundledDeps(dir) require.Error(t, err) From bdc12a90e3b0467ab3e5527cff2f260668090765 Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Fri, 12 Jun 2026 11:23:51 +0800 Subject: [PATCH 6/6] fix: use 0o750 dir permissions (gosec G301) --- .../internal/project/service_target_agent_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go index a5fbd23d8ba..ec3b87fe8b1 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent_test.go @@ -1912,7 +1912,7 @@ func TestValidatePythonBundledDeps_NoDepsInstalled(t *testing.T) { func TestValidatePythonBundledDeps_TopLevelDistInfo(t *testing.T) { dir := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600)) - require.NoError(t, os.MkdirAll(filepath.Join(dir, "azure_ai_agents-1.0.dist-info"), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "azure_ai_agents-1.0.dist-info"), 0o750)) err := validatePythonBundledDeps(dir) require.NoError(t, err) @@ -1922,7 +1922,7 @@ func TestValidatePythonBundledDeps_SubdirDistInfo(t *testing.T) { dir := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("azure-ai-agents>=1.0\n"), 0600)) // Installed into vendor/ subdir - require.NoError(t, os.MkdirAll(filepath.Join(dir, "vendor", "azure_ai_agents-1.0.dist-info"), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "vendor", "azure_ai_agents-1.0.dist-info"), 0o750)) err := validatePythonBundledDeps(dir) require.NoError(t, err)