Skip to content

feat: add .agentignore support for agent code deploy packaging#8223

Merged
trangevi merged 10 commits into
Azure:mainfrom
v1212:feature/agentignore
May 20, 2026
Merged

feat: add .agentignore support for agent code deploy packaging#8223
trangevi merged 10 commits into
Azure:mainfrom
v1212:feature/agentignore

Conversation

@v1212
Copy link
Copy Markdown
Collaborator

@v1212 v1212 commented May 18, 2026

Summary

Closes #8170

  • Adds .agentignore file support (.gitignore syntax) for user-configurable file exclusion during azd ai agent deploy code packaging
  • When no .agentignore exists, built-in defaults apply (Python/.NET/Node artifacts, azd tooling files)
  • When .agentignore is present, user rules fully replace defaults (like .dockerignore semantics)
  • Default .agentignore includes security exclusions (.env, .azure/, .git/); users who customize should keep them
  • azd ai agent init (both template and from-code flows) now generates a default .agentignore
  • Shows polling attempt progress during waitForAgentActive so users know deployment is not stuck

Design

  • Replace semantics when user file exists: User has full control over exclusions
  • Default .agentignore includes security exclusions: .env, .azure/, .git/ are in defaults; users who customize should keep them
  • Single source of truth: DefaultAgentIgnoreContent() raw string used both for runtime defaults and generated file
  • Uses github.com/denormal/go-gitignore (already used by azd core for .azdignore)
  • No impact on existing flows: Without .agentignore file, behavior is identical to before

Testing

  • Unit tests cover: no-file defaults, user override, negation patterns, symlink rejection, UTF-8 BOM handling, empty file
  • All existing tests pass unchanged

E2E Regression Test Results

Environment: azd 1.23.15, extension 0.1.31-preview, northcentralus
Method: Interactive azd ai agent init + azd deploy (no manual file modifications)

# Template Language Deploy Protocol Result
1 Hello World Invocations C# (.NET 10) ZIP / remote_build invocations Passed
2 Hello World Responses Python ZIP / remote_build responses Passed
3 Hello World Responses (Agent Framework) C# (.NET 10) ZIP / remote_build responses Passed
4 From-code (vnext-responses-streaming) Python ZIP / remote_build responses Passed
5 Hello World Responses (template) Python ZIP / remote_build responses Passed
6 Hello World Invocations (template) C# (.NET 10) ZIP / remote_build invocations Passed
7 Agent with MCP Tools (Docker/ACR) Python Docker / ACR responses Passed

All 7 tests passed: init → deploy → invoke (verified HTTP 200 with correct agent response).

@v1212 v1212 marked this pull request as ready for review May 18, 2026 08:22
Copilot AI review requested due to automatic review settings May 18, 2026 08:22
v1212

This comment was marked as outdated.

Copy link
Copy Markdown
Collaborator Author

@v1212 v1212 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds .agentignore support (gitignore syntax) to control which files are excluded from agent code-deploy ZIP packaging, while keeping security/metadata exclusions enforced. It also improves deploy polling UX by displaying polling attempt progress, and ensures azd ai agent init generates a default .agentignore when missing.

Changes:

  • Implement .agentignore parsing/matching with fallback default exclusions and non-overridable security exclusions.
  • Update code packaging to use the ignore matcher instead of hardcoded exclude lists.
  • Generate a default .agentignore during agent init flows and show polling attempt counts while waiting for agent activation.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Switch ZIP packaging exclusions to .agentignore-driven matcher; add polling attempt progress output.
cli/azd/extensions/azure.ai.agents/internal/project/agentignore.go New ignore matcher implementation with defaults + enforced security/metadata exclusions and BOM handling.
cli/azd/extensions/azure.ai.agents/internal/project/agentignore_test.go Unit tests covering default behavior, overrides, negation, security enforcement, symlink rejection, BOM, and metadata exclusions.
cli/azd/extensions/azure.ai.agents/internal/cmd/init.go Generate a default .agentignore on init if missing.
cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Generate a default .agentignore on init-from-code if missing.
cli/azd/extensions/azure.ai.agents/go.mod Add go-gitignore dependency (currently marked indirect).
cli/azd/extensions/azure.ai.agents/go.sum Add sums for newly introduced dependencies.
cli/azd/.vscode/cspell.yaml Add agentignore to spelling dictionary.

Comment thread cli/azd/extensions/azure.ai.agents/internal/project/agentignore.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/go.mod
Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses the same go-gitignore library as core azd, security model is solid (non-negotiable exclusions can't be overridden), and tests cover the important edge cases well. Two consistency items below.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/project/agentignore.go
@v1212
Copy link
Copy Markdown
Collaborator Author

v1212 commented May 18, 2026

E2E Regression Test Results (PR #8223)

Environment: azd 1.23.15, extension 0.1.31-preview, northcentralus region
Method: Interactive azd ai agent init + azd deploy (no manual file modifications)

All 7 Tests Passed

# Template Language Deploy Protocol Time Result
1 Hello World Invocations C# (.NET 10) ZIP / remote_build invocations 1m25s
2 Hello World Responses Python ZIP / remote_build responses 2m35s
3 Hello World Responses (Agent Framework) C# (.NET 10) ZIP / remote_build responses 6m12s
4 From-code (vnext-responses-streaming) Python ZIP / remote_build responses 2m36s
5 Hello World Responses (template) Python ZIP / remote_build responses 20s
6 Hello World Invocations (template) C# (.NET 10) ZIP / remote_build invocations 1m25s
7 Agent with MCP Tools (Docker/ACR) Python Docker / ACR responses 4m11s

Test Details

  • Tests 1-6: Used existing Foundry project (foundry-test-0518, northcentralus) with azd deploy
  • Test 7: Full azd up flow provisioning new ACR + Docker image push + deploy
  • Model: gpt-5.4-mini on all tests
  • Invocation: All agents responded correctly within 4 minutes of deploy completion
  • Cleanup: All agents cleaned up with azd down --force --purge

Conclusion

The .agentignore feature does not cause regressions in the agent init/deploy/invoke flow across Python and C# templates with both ZIP (remote_build) and Docker (ACR) deployment methods.

Comment thread cli/azd/extensions/azure.ai.agents/internal/project/agentignore.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/project/agentignore.go Outdated
@v1212 v1212 force-pushed the feature/agentignore branch from 76319d1 to 2b595b9 Compare May 19, 2026 02:00
@trangevi trangevi enabled auto-merge (squash) May 19, 2026 15:55
@trangevi
Copy link
Copy Markdown
Member

/check-enforcer override

Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after force-push (HEAD: 2b595b9)

Thanks for the substantial rework. The intentional pivot to a fully user-configurable model (per @v1212's note and the refactor: remove forced exclusions commit) is reasonable as a design choice — but it makes the PR description materially misleading, which is the main item to address before merge. The rest below is non-blocking polish.

🔴 Must fix before merge — PR description / implementation mismatch

The PR description still says:

"Security exclusions (.env, .azure/, .git/) remain non-negotiable and cannot be overridden with ! negation"

…but agentignore_test.go now explicitly asserts the opposite, and the rewrite intentionally removed the enforcement layer. Future readers (reviewers, maintainers, security auditors, users finding this PR via git blame) will be misled. Please update the description to reflect the actual .dockerignore-style replace semantics — something like:

"Security-sensitive paths (.env, .azure/, .git/) ship in the default .agentignore template, but are fully user-configurable. When a user provides their own .agentignore, it replaces the defaults entirely — users are responsible for re-listing any paths they want excluded."

While you're at it, consider adding a short note to the generated .agentignore header (or README) that calls this out explicitly so users understand the contract when they first edit the file.

🟠 Open items from prior review (non-blocking)

  1. packageCodeDeploy error classificationnewAgentIgnoreMatcher failures still come back as bare fmt.Errorf in service_target_agent.go. Per cli/azd/extensions/azure.ai.agents/AGENTS.md, orchestration-layer failures should be classified with exterrors.Dependency(...) so they pick up the proper code + user suggestion.

  2. ctx not threaded into I/O helpersnewAgentIgnoreMatcher(srcDir) and loadAgentIgnore(srcDir) perform file I/O without a context.Context. cli/azd/AGENTS.md calls for ctx as the first parameter of I/O functions. The caller (packageCodeDeploy) already has one available.

  3. Symlinked-directory traversal in the walker.agentignore itself correctly rejects symlinks, but the ZIP walker in service_target_agent.go only checks symlink-ness for files. A directory symlink (e.g., subdir -> /etc) would let the walker descend out of the project root. Suggest adding if d.IsDir() && d.Type()&fs.ModeSymlink != 0 { return filepath.SkipDir } in the walker callback.

  4. Polling progress display — the attempt N/30 line is printed once per iteration. If the progress channel in the extension UI doesn't collapse these in-place, users will see ~30 stacked lines. Worth a quick smoke test in a real terminal; if it does spam, switch to a spinner or carriage-return inline update.

  5. Discoverabilityazd ai agent init --help / azd ai agent deploy --help and the extension's CHANGELOG/README still don't mention .agentignore. Recommend a short paragraph in each (and a CHANGELOG entry for the next release).

  6. Subdirectory .agentignore not supported — unlike .gitignore, only the root file is read. Worth a one-line note in the generated default's header so users with a .gitignore mental model aren't surprised.

✅ Resolved since prior review

  • srcDir unused field removed
  • go mod tidy correction (no more // indirect)
  • osutil.PermissionFile used consistently
  • Default exclusions unified into a single source of truth (DefaultAgentIgnoreContent())
  • Tests added for negation, empty file, UTF-8 BOM, symlink rejection, max file size
  • init and init_from_code both generate .agentignore
  • Polling counter wired up
  • go fix modernization applied

CI

27/28 checks green; only the approval gate is open. Nice work tightening this up — once the PR description is updated, this is ready for another pass from @trangevi.

@trangevi trangevi disabled auto-merge May 19, 2026 16:24
Comment thread cli/azd/.vscode/cspell.yaml Outdated
Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after 9e37929

Thanks — the updated PR description now correctly reflects the .dockerignore-style replace semantics. That clears my prior blocking concern. ✅

The latest commit shortened the polling message text, which helps readability. The display still emits one line per attempt though, so if the extension's progress channel doesn't collapse them in-place, terminals will still show ~30 stacked lines. Worth a quick smoke test in an interactive run; if it does stack, a spinner / inline carriage-return update would be a nicer UX.

Re-flagging open non-blocking items from my prior pass

None of these are merge-blockers from my side, but listing them in one place so they're easy to triage as a follow-up issue or quick polish commit:

  1. packageCodeDeploy error classificationnewAgentIgnoreMatcher failures still surface as bare fmt.Errorf in service_target_agent.go. Per cli/azd/extensions/azure.ai.agents/AGENTS.md, orchestration-layer failures should be wrapped with exterrors.Dependency(...) so users get the proper code + suggestion.

  2. ctx not threaded into I/O helpersnewAgentIgnoreMatcher(srcDir) and loadAgentIgnore(srcDir) perform file I/O without a context.Context. cli/azd/AGENTS.md calls for ctx as the first parameter on I/O functions; the caller (packageCodeDeploy) already has one.

  3. Symlinked-directory traversal in the ZIP walker.agentignore itself correctly rejects symlinks, but the walker only checks symlink-ness for files. A directory symlink (e.g., subdir -> /etc) would let the walker descend outside the project root. Suggest adding if d.IsDir() && d.Type()&fs.ModeSymlink != 0 { return filepath.SkipDir } in the walker callback.

  4. Polling progress display — text is shorter now ✅, but frequency unchanged. Verify whether the extension's progress channel updates in place or appends; if it appends, switch to a spinner.

  5. Discoverabilityazd ai agent init --help, azd ai agent deploy --help, the extension README, and the next CHANGELOG entry should all mention .agentignore. Right now this feature is essentially invisible unless users notice the file on disk.

  6. Subdirectory .agentignore not supported — unlike .gitignore, only the root file is read. A one-line note in the generated default's header ("only the root .agentignore is read; subdirectory files are ignored") would prevent surprise for users with a .gitignore mental model.

Also supporting @trangevi's cspell point

Agree with @trangevi on cli/azd/.vscode/cspell.yaml — per repo memory and the existing pattern in that file, extension-scoped words should live in the per-file overrides section (or in the extension's own cspell config) rather than the core azd global list. The term agentignore is only meaningful inside cli/azd/extensions/azure.ai.agents/, so scoping it accordingly keeps the global dictionary lean. Easy follow-up.

Status

CI is green (29/29). Outstanding blocker is @trangevi's CHANGES_REQUESTED on the cspell placement — once that's resolved, this should be good to merge from my side.

@v1212
Copy link
Copy Markdown
Collaborator Author

v1212 commented May 20, 2026

Thanks for the review and detailed suggestions. Here are improvements in the latest commit:

  1. packageCodeDeploy error classification — wrapped with exterrors.Dependency() to provide proper code + suggestion
  2. ctx not threaded into I/O helpersctx context.Context is now passed as the first parameter following existing practice
  3. Symlinked-directory traversal in the ZIP walker — added check in the walker callback to skip symlinked directories
  4. Polling progress display — verified: the current CLI UI updates in place via ANSI cursor control and won't show multiple stacked lines
  5. Discoverability — added entry in CHANGELOG and init command's Long description mentioning .agentignore
  6. Subdirectory .agentignore not supported — added note in the generated default's header to remind users

Also moved agentignore from global cli/azd/.vscode/cspell.yaml to extension-specific cli/azd/extensions/azure.ai.agents/cspell.yaml per @trangevi's feedback.

Jian Wu added 10 commits May 20, 2026 14:47
…#8170)

Add user-configurable file exclusion for code deploy ZIP packaging using
.gitignore syntax. When no .agentignore file exists, built-in defaults
apply (Python, .NET, Node artifacts). When present, user rules replace
defaults while security exclusions (.env, .azure/, .git/) remain
non-negotiable. azd ai agent init now generates a default .agentignore.
… package

Add metadataExclusions to isSecurityExcluded so that agent.yaml,
agent.manifest.yaml, azure.yaml, and .agentignore are never included
in the ZIP package regardless of user's .agentignore content. This
prevents a regression where these files could leak into the package
if the user provides a custom .agentignore without listing them.
- Remove duplicate defaultExclusions slice; reuse DefaultAgentIgnoreContent()
  as single source of truth (jongio comment Azure#4)
- Remove unused srcDir field from agentIgnoreMatcher (Copilot comment)
- Run go mod tidy to fix go-gitignore indirect marker (Copilot comment)
- Use osutil.PermissionFile instead of raw 0644 in init_from_code.go (jongio comment Azure#3)
Address review feedback from trangevi:
- Remove non-configurable security/metadata forced exclusions. All
  exclusions are now configurable via .agentignore (defaults still
  include .env, .azure/, .git/ but users can override with negation).
- Rewrite DefaultAgentIgnoreContent() as a raw string literal for
  readability (replaces verbose sb.WriteString calls).
- Add Dockerfile and .dockerignore to default exclusions (not needed
  in code deploy path).
- Simplify agentIgnoreMatcher: remove isSecurityExcluded layer,
  securityExclusions, metadataExclusions — single matcher handles all.
…rors, symlinks, cspell scope, discoverability)
@v1212 v1212 force-pushed the feature/agentignore branch from b4b83b1 to 8f14db5 Compare May 20, 2026 06:48
Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ✅

Thanks @v1212 — the latest commit cleanly addresses every item from my prior pass:

  • packageCodeDeploy failures now classified via exterrors.Dependency(...)
  • ctx context.Context threaded through newAgentIgnoreMatcher and loadAgentIgnore
  • ✅ Walker now skips symlinked directories (SkipDir on d.IsDir() && d.Type()&fs.ModeSymlink != 0)
  • ✅ Polling display verified — progress callback updates in place via ANSI, no per-line stacking
  • ✅ CHANGELOG entry added and init command's Long description now mentions .agentignore
  • ✅ Subdirectory-not-supported note added to the generated default's header
  • agentignore moved from core cli/azd/.vscode/cspell.yaml to the extension's own cspell config per @trangevi

PR description matches the implementation, tests are comprehensive, and the design rationale (replace semantics, default template carries security exclusions, single source of truth) is clearly laid out in the body. Nice work iterating through the rounds of feedback.

@trangevi trangevi enabled auto-merge (squash) May 20, 2026 20:02
@trangevi
Copy link
Copy Markdown
Member

/check-enforcer override

@JeffreyCA JeffreyCA added the ext-agents azure.ai.{agents,connections,inspector,projects,routines,skills,toolboxes} extensions label May 20, 2026
@trangevi trangevi merged commit 5c482c7 into Azure:main May 20, 2026
22 of 23 checks passed
Copilot AI pushed a commit that referenced this pull request May 21, 2026
* feat: add .agentignore support for agent code deploy packaging (#8170)

Add user-configurable file exclusion for code deploy ZIP packaging using
.gitignore syntax. When no .agentignore file exists, built-in defaults
apply (Python, .NET, Node artifacts). When present, user rules replace
defaults while security exclusions (.env, .azure/, .git/) remain
non-negotiable. azd ai agent init now generates a default .agentignore.

* fix: address CI issues - cspell, gosec, go fix modernization

* refactor: clean up security exclusions list and add nested path tests

* feat: show polling attempt progress during waitForAgentActive

* fix: always exclude metadata files (agent.yaml, azure.yaml) from code package

Add metadataExclusions to isSecurityExcluded so that agent.yaml,
agent.manifest.yaml, azure.yaml, and .agentignore are never included
in the ZIP package regardless of user's .agentignore content. This
prevents a regression where these files could leak into the package
if the user provides a custom .agentignore without listing them.

* refactor: address review comments on .agentignore implementation

- Remove duplicate defaultExclusions slice; reuse DefaultAgentIgnoreContent()
  as single source of truth (jongio comment #4)
- Remove unused srcDir field from agentIgnoreMatcher (Copilot comment)
- Run go mod tidy to fix go-gitignore indirect marker (Copilot comment)
- Use osutil.PermissionFile instead of raw 0644 in init_from_code.go (jongio comment #3)

* fix: apply go fix modernization (slices.Contains)

* refactor: remove forced exclusions, use raw string for defaults

Address review feedback from trangevi:
- Remove non-configurable security/metadata forced exclusions. All
  exclusions are now configurable via .agentignore (defaults still
  include .env, .azure/, .git/ but users can override with negation).
- Rewrite DefaultAgentIgnoreContent() as a raw string literal for
  readability (replaces verbose sb.WriteString calls).
- Add Dockerfile and .dockerignore to default exclusions (not needed
  in code deploy path).
- Simplify agentIgnoreMatcher: remove isSecurityExcluded layer,
  securityExclusions, metadataExclusions — single matcher handles all.

* chore: shorten polling progress message for better terminal display

* refactor: address reviewer feedback on .agentignore (ctx param, exterrors, symlinks, cspell scope, discoverability)

---------

Co-authored-by: Jian Wu <wujia@microsoft.com>
Co-authored-by: therealjohn <1501196+therealjohn@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-agents azure.ai.{agents,connections,inspector,projects,routines,skills,toolboxes} extensions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: .gitignore-style handling in azd code deploy (.azdignore support)

7 participants