fix: add pre-deploy validation for Python bundled mode#8624
Conversation
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>
📋 Prioritization NoteThanks for the contribution! The linked issue isn't in the current milestone yet. |
There was a problem hiding this comment.
Pull request overview
Adds guardrails for Python “bundled” dependency resolution in the azure.ai.agents extension so users get an actionable failure before deploy (and a post-init reminder) instead of a runtime ModuleNotFoundError.
Changes:
- Add a deploy-time validation for Python bundled mode that checks for installed dependencies (
*.dist-info) whenrequirements.txtis present. - Add a post-init note for Python bundled mode with a
pip install --target .command tailored to the selected runtime. - Introduce a new external error code for the missing-bundled-deps scenario.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go |
Adds Python bundled dependency validation during code packaging and returns a typed dependency error when missing. |
cli/azd/extensions/azure.ai.agents/internal/exterrors/codes.go |
Adds a new error code used by the bundled dependency validation failure. |
cli/azd/extensions/azure.ai.agents/internal/cmd/init_validate.go |
Prints a post-init hint for Python bundled projects that have requirements.txt. |
- 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>
- 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>
- 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>
glharper
left a comment
There was a problem hiding this comment.
Review: Python bundled mode pre-deploy validation
Overall: Solid, well-tested fix. Error handling follows the exterrors convention correctly (structured error created once, not re-wrapped), os.ErrNotExist is handled, paths are shell-quoted, and tests use a stdout-capture pipe rather than writing to real os.Stdout. One issue worth addressing before merge.
🟡 Medium — Deploy-error pip command is inconsistent with the init hint (can install wrong-ABI wheels)
The error returned by validatePythonBundledDeps (internal/project/service_target_agent.go) suggests:
pip install -r requirements.txt -t "<srcDir>" --platform manylinux_2_17_x86_64 \
--platform linux_x86_64 --platform any --implementation cp --only-binary=:all:
But the init hint (internal/cmd/init_validate.go) deliberately includes --python-version (and --upgrade):
... --python-version 3.14 --implementation cp --only-binary=:all: --upgrade
A user who hits the deploy block and copies the suggested command will have pip resolve wheels for the local interpreter's version tag, not the Linux target's Python — which can install ABI-mismatched wheels and reproduce the very ModuleNotFoundError this PR prevents. The caller (packageCodeDeploy) already has agentDef.CodeConfiguration.Runtime available, so thread the version into validatePythonBundledDeps and add --python-version <ver> to keep both commands consistent.
🟢 Minor — Description/doc-comment slightly overstate behavior
- The PR description says "IO errors on ReadDir are surfaced (not silently skipped)." That's true only for the top-level
ReadDir. The one-level-deep scan silentlycontinues on error. Reasonable as a best-effort choice, but the description should match. - The doc comment "avoids false positives" overstates the guarantee: dependencies installed more than one subdirectory deep, or a
requirements.txtthat only uses-r other.txtindirection, would trigger a false deploy-blocking error. The commonpip install --target .case is handled correctly, so this is acceptable as a heuristic — just worth not claiming it's false-positive-free.
✅ Confirmed correct
.dist-infodetection matches pip--targetlayout; the default.agentignoredoesn't strip.dist-info, so validated deps land in the ZIP.- New
CodeBundledDepsNotFoundcode is lowercase snake_case;manylinuxadded to cspell. - Tests don't write to real stdout; the error-code test properly asserts the new code.
Recommendation: Add --python-version to the deploy-error suggestion so it matches the init hint; everything else looks good.
Problem
When users select "Bundled" during
azd ai agent initbut forget to runpip install --target .beforeazd deploy, the deployment succeeds but the container crashes at runtime withModuleNotFoundError(see #8610).Fix
Post-init hint (
azd ai agent init): When bundled mode is selected for a Python project withrequirements.txt, print the correct platform-specificpip installexample command targeting the deployment platform (Linux x86_64). Includes a note that--only-binary=:all:may need to be removed for packages lacking pre-built wheels.Pre-deploy check (
azd deploy): When Python + bundled mode is configured andrequirements.txtexists with content but no.dist-infodirectories are found in the source directory (top-level or one subdirectory deep), fail with a clear error message and remediation steps.Changes
internal/cmd/init_validate.go— AddedvalidateBundledHint()to print pip install guidance after initinternal/project/service_target_agent.go— AddedvalidatePythonBundledDeps()pre-deploy checkinternal/exterrors/codes.go— AddedCodeBundledDepsNotFounderror codeinternal/cmd/init_validate_test.go— 4 new tests for the hint functioninternal/project/service_target_agent_test.go— 6 new tests for the deploy checkDesign Decisions
.dist-infodir exists (top-level or one level deep). IO errors on ReadDir are surfaced (not silently skipped). Onlyos.ErrNotExistfor requirements.txt is ignored.srcDiris quoted in all user-facing commands to handle paths with spacesTest Results
Unit tests pass (
go test ./... -count=1).Integration Tests (Windows, manual validation)
All 5 scenarios tested on Windows using extension built from this branch (
bdc12a90e, 2026-06-12):initpip installcommand including--platform manylinux_2_17_x86_64flags and quoted pathdeploywithout depsbundled mode is configured but no installed packages were found in the source directorydeploywith deps.dist-infopresent), deploy passes Packaging and proceeds to Publishingremote_builddeploy (non-regression)init(non-regression)dotnet_10 --dep-resolution bundledcompletes without printing the Python bundled hintNote: Full end-to-end deploy to Azure was not tested (only validation gate behavior), as the validation fires locally before any Azure API calls.
Fixes #8610