Skip to content

Commit a2842df

Browse files
spboyerCopilot
andcommitted
docs: add PR review patterns to AGENTS.md
Add lessons learned from team and Copilot reviews across PRs #7290, #7251, #7250, #7247, #7236, #7235, #7202, #7039 as agent instructions to prevent recurring review findings. New/expanded sections: - Error handling: ErrorWithSuggestion field completeness, telemetry service attribution, scope-agnostic messages, link/suggestion parity, stale data in polling loops - Architecture boundaries: pkg/project target-agnostic, extension docs separation, env var verification against source code - Output formatting: shell-safe quoted paths, consistent JSON types - Path safety: traversal validation, quoted paths in messages - Code organization: extract shared logic across scopes - Documentation standards: help text consistency, no dead references, PR description accuracy - Testing best practices: test YAML rules e2e, extract shared helpers, correct env vars (AZD_FORCE_TTY, NO_COLOR), TypeScript patterns, reasonable timeouts, cross-platform paths, test new JSON fields - CI / GitHub Actions: permissions blocks, PATH handling, cross-workflow artifacts, prefer ADO for secrets, no placeholder steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b2b1edf commit a2842df

File tree

1 file changed

+48
-1
lines changed

1 file changed

+48
-1
lines changed

cli/azd/AGENTS.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Azure Developer CLI (azd) - Agent Instructions
22

3-
<!-- cspell:ignore Errorf Chdir azapi gofmt golangci stdlib -->
3+
<!-- cspell:ignore Errorf Chdir azapi gofmt golangci stdlib strconv Readdirnames -->
44

55
Instructions for AI coding agents working with the Azure Developer CLI.
66

@@ -154,24 +154,46 @@ func (a *myAction) Run(ctx context.Context) (*actions.ActionResult, error) {
154154
155155
- Commands can support multiple output formats via `--output` flag like `json` and`table`
156156
- Use structured output for machine consumption
157+
- **Shell-safe output**: When emitting shell commands in user-facing messages (e.g., `cd <path>`), quote paths that may contain spaces. Use `fmt.Sprintf("cd %q", path)` or conditionally wrap in quotes
158+
- **Consistent JSON types**: When adding fields to JSON output (`--output json`), match the types used by similar fields across commands. Don't mix `*time.Time` and custom timestamp wrappers (e.g., `*RFC3339Time`) in the same API surface
157159
158160
### Code Organization
159161
160162
- **Import order**: stdlib → external → azure/azd internal → local
161163
- **Complex packages**: Consider using `types.go` for shared type definitions (3+ types)
162164
- **Context propagation**: Pass `ctx context.Context` as the first parameter to functions that do I/O or may need cancellation
165+
- **Don't duplicate logic across scopes**: When similar logic exists for multiple deployment scopes (e.g., resource group + subscription), extract shared helpers (e.g., `filterActiveDeployments()`) instead of copying code between scope implementations
163166
164167
### Error Handling
165168
166169
- Wrap errors with `fmt.Errorf("context: %w", err)` to preserve the error chain
167170
- Consider using `internal.ErrorWithSuggestion` for straightforward, deterministic user-fixable issues
168171
- Handle context cancellations appropriately
172+
- **`ErrorWithSuggestion` completeness**: When returning `ErrorWithSuggestion`, populate **all** relevant fields (`Err`, `Suggestion`, `Message`, `Links`). `ErrorMiddleware` skips the YAML error-suggestion pipeline for errors already typed as `ErrorWithSuggestion`, so leaving fields empty means the user misses guidance that the YAML rule would have provided
173+
- **Telemetry service attribution**: Only set `error.service.name` (e.g., `"aad"`) when an external service actually returned the error. For locally-generated errors (e.g., "not logged in" state checks), don't attribute to an external service — use a local classification instead
174+
- **Scope-agnostic error messages**: Error messages and suggestions in `error_suggestions.yaml` should work across all deployment scopes (subscription, resource group, etc.). Use "target scope" or "deployment scope" instead of hardcoding "resource group"
175+
- **Match links to suggestion text**: If a suggestion mentions multiple tools (e.g., "Docker or Podman"), the `links:` list must include URLs for all of them. Don't mention options you don't link to
176+
- **Stale data in polling loops**: When polling for state changes (e.g., waiting for active deployments), refresh display data (names, counts) from each poll result rather than capturing it once at the start
177+
178+
### Architecture Boundaries
179+
180+
- **`ProjectManager` is target-agnostic**: `project_manager.go` should not contain service-target-specific logic (e.g., Container Apps details, Docker checks). Target-specific behavior belongs in the target implementations (e.g., `service_target_containerapp.go`) or in the `error_suggestions.yaml` pipeline. The project manager is an interface for service management and should not make assumptions about which target is running
181+
- **Extension-specific documentation**: Keep extension-specific environment variables and configuration documented in the extension's own docs, not in core azd reference docs, unless they are consumed by the core CLI itself
182+
- **Verify env vars against source**: When documenting environment variables, verify the actual parsing method in code — `os.LookupEnv` (presence-only) vs `strconv.ParseBool` (true/false) vs `time.ParseDuration` vs integer seconds. Document the expected format and default value accurately
183+
184+
### Path Safety
185+
186+
- **Validate derived paths**: When deriving directory names from user input or template paths, always validate the result is not `.`, `..`, empty, or contains path separators. These can cause path traversal outside the working directory
187+
- **Quote paths in user-facing output**: Shell commands in suggestions, follow-up messages, and error hints should quote file paths that may contain spaces
169188
170189
### Documentation Standards
171190
172191
- Public functions and types must have Go doc comments
173192
- Comments should start with the function/type name
174193
- Document non-obvious dependencies or assumptions
194+
- **Help text consistency**: When changing command behavior, update **all** related help text — `Short`, `Long`, custom description functions used by help generators, and usage snapshot files. Stale help text that contradicts the actual behavior is a common review finding
195+
- **No dead references**: Don't reference files, scripts, directories, or workflows that don't exist in the PR. If a README lists `scripts/generate-report.ts`, it must exist. If a CI table lists `eval-human.yml`, it must be included
196+
- **PR description accuracy**: Keep the PR description in sync with the actual implementation. If the description says "server-side filtering" but the code does client-side filtering, update the description
175197
176198
### Modern Go
177199
@@ -196,6 +218,21 @@ This project uses Go 1.26. Use modern standard library features:
196218
- Use `t.Chdir(dir)` instead of `os.Chdir` plus a deferred restore in tests
197219
- Run `go fix ./...` before committing; CI enforces these modernizations
198220
221+
### Testing Best Practices
222+
223+
- **Test the actual rules, not just the framework**: When adding YAML-based error suggestion rules, write tests that exercise the rules end-to-end through the pipeline, not just tests that validate the framework's generic matching behavior
224+
- **Extract shared test helpers**: Don't duplicate test utilities across files. Extract common helpers (e.g., shell wrappers, auth token fetchers, CLI runners) into shared `test_utils` packages. Duplication across 3+ files should always be refactored
225+
- **Use correct env vars for testing**:
226+
- Non-interactive mode: `AZD_FORCE_TTY=false` (not `AZD_DEBUG_FORCE_NO_TTY`, which doesn't exist)
227+
- No-prompt mode: use the `--no-prompt` flag for core azd commands; `AZD_NO_PROMPT=true` is only used for propagating no-prompt into extension subprocesses
228+
- Suppress color: `NO_COLOR=1` — always set in test environments to prevent ANSI escape codes from breaking assertions
229+
- **TypeScript test patterns**: Use `catch (e: unknown)` with type assertions, not `catch (e: any)` which bypasses strict mode
230+
- **Reasonable timeouts**: Set test timeouts proportional to expected execution time. Don't use 5-minute timeouts for tests that shell out to `azd --help` (which completes in seconds)
231+
- **Efficient directory checks**: To check if a directory is empty, use `os.Open` + `f.Readdirnames(1)` instead of `os.ReadDir` which reads the entire listing into memory
232+
- **Cross-platform paths**: When resolving binary paths in tests, handle `.exe` suffix on Windows (e.g., `azd` vs `azd.exe` via `process.platform === "win32"`)
233+
- **Test new JSON fields**: When adding fields to JSON command output (e.g., `expiresOn` in `azd auth status --output json`), add a test asserting the field's presence and format
234+
- **No unused dependencies**: Don't add npm/pip packages that aren't imported anywhere. Remove dead `devDependencies` before submitting
235+
199236
## MCP Tools
200237
201238
Tools follow `server.ServerTool` interface from `github.com/mark3labs/mcp-go/server`:
@@ -227,3 +264,13 @@ Feature-specific docs are in `docs/` — refer to them as needed. Some key docs
227264
- `docs/extensions/extension-framework.md` - Extension development using gRPC extension framework
228265
- `docs/style-guidelines/guiding-principles.md` - Design principles
229266
- `docs/tracing-in-azd.md` - Tracing/telemetry guidelines
267+
268+
## CI / GitHub Actions
269+
270+
When creating or modifying GitHub Actions workflows:
271+
272+
- **Always declare `permissions:`** explicitly with least-privilege (e.g., `contents: read`). All workflows in the repo should have this block for consistency
273+
- **Don't overwrite `PATH`** using `${{ env.PATH }}` — it's not defined in GitHub Actions expressions and will wipe the real PATH. Use `echo "$DIR" >> $GITHUB_PATH` instead
274+
- **Cross-workflow artifacts**: `actions/download-artifact@v4` without `run-id` only downloads artifacts from the *current* workflow run. Cross-workflow artifact sharing requires `run-id` and `repository` parameters
275+
- **Prefer Azure DevOps pipelines** for jobs that need secrets or Azure credentials — the team uses internal ADO pipelines for authenticated workloads in this public repo
276+
- **No placeholder steps**: Don't add workflow steps that echo "TODO" or list directories without producing output. If downstream steps depend on generated files, implement the generation or remove the dependency

0 commit comments

Comments
 (0)