Add azd ai dataset extension for Foundry datasets - #9499
Add azd ai dataset extension for Foundry datasets#9499Mohamed Hessien (m7md7sien) wants to merge 33 commits into
Conversation
Datasets are a general Foundry asset that fine-tuning and other scenarios need as much as evaluation does, so the CRUD group ships as its own extension rather than inside azure.ai.evaluations. Generation stays with the evaluation extension, because it writes that extension's config file. Standalone: no dependency on azure.ai.evaluations, its own go module, lint workflow and release pipeline.
Adds internal/messages, holding the extension's errors, warnings and status lines behind one named function each, grouped under Datasets, Config, Output and Talking to the service. Wording is unchanged; the call sites now name the situation instead of spelling out the sentence. Cobra help, wire constants and table headers stay where they are.
Three things carried over from the extension this was carved out of. The User-Agent announced azure-ai-evaluations and the debug log was written to azd-ai-eval-<date>.log, so a dataset request could not be told from an eval one in either a service log or a local one. The FORMAT column was worse than wrong: format is a field the API accepts on upload and never sends back, so the column was blank for every dataset ever listed. It shows type, which the service does return.
Carved from the same code, so it had the same two: a nil Environment or GetValue response panicking inside the endpoint cascade, and an azd client abandoned when endpoint or credential setup failed.
Same credential failure and same 401/403 path as the eval extension, which this was carved from.
Same intermittent credential failure as the eval extension.
Same three as the eval extension, which this was carved from.
|
Azure Pipelines: 7 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
A BOM at the head of a .jsonl is uploaded as part of the first row's first key, so every consumer of the dataset sees one malformed record -- and nothing fails until something tries to read that row. Notepad, VS Code on Windows and PowerShell all write one. A file holding nothing but a BOM is still empty, and is still refused as such.
Found by bug-bashing the off-path cases. A missing dataset reported "has no versions", which is not what happened -- a dataset cannot exist with no versions, and the service answers an unknown name with an empty list rather than a 404. Listing its versions was worse: it said "No datasets found", as though the project had none at all. Both now say the name is not a dataset and point at the listing, matching how the evaluations extension already answers the same question. A name with a space took a file upload and then a 400 carrying four levels of nested JSON, with the sentence that matters at the bottom. The rule is simple and documented, so it is checked before the round trip.
A mistyped path surfaced as 'GetFileAttributesEx <path>: The system cannot find the file specified.' The syscall name is noise to the person who mistyped it.
--from-file <file> resolved to the file's DIRECTORY, and the upload helper then took whichever .jsonl sorted first. Pointing at one dataset in a folder holding several registered a different one under that name. A test pinned the old behaviour: 'a file resolves to the directory the upload scans'.
The unit tests cover the client layer and the helpers; nothing exercised the command surface a user touches - flag parsing, exit codes, the rendered table, and whether -o json emits something a script can consume. 18 tests behind the live tag, gated on AZURE_AI_DATASET_E2E_LIVE, type-checked by ci-test.ps1 like the eval extension's suites.
|
Azure Pipelines: 7 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds a standalone Foundry dataset extension with CRUD/versioning commands, API clients, tests, documentation, and release automation.
Changes:
- Adds dataset creation, updates, listing, inspection, deletion, and version listing.
- Adds endpoint resolution, validation, structured errors, and JSON/table output.
- Adds unit/live tests plus build, lint, and release infrastructure.
Reviewed changes
Copilot reviewed 49 out of 50 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
eng/pipelines/release-ext-azure-ai-dataset.yml |
Adds extension release pipeline. |
cli/azd/extensions/azure.ai.dataset/version.txt |
Sets initial beta version. |
cli/azd/extensions/azure.ai.dataset/tests/cli/harness_test.go |
Adds live CLI harness. |
cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go |
Tests dataset CLI workflows. |
cli/azd/extensions/azure.ai.dataset/README.md |
Documents installation and commands. |
cli/azd/extensions/azure.ai.dataset/main.go |
Adds extension entry point. |
cli/azd/extensions/azure.ai.dataset/internal/version/version.go |
Defines build metadata. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/version_test.go |
Tests version handling. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/uri_test.go |
Tests URI resolution. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/upload_version_test.go |
Tests conflict-aware uploads. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go |
Implements dataset and storage operations. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations_wire_test.go |
Tests API request contracts. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/models.go |
Defines API models and file handling. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/list.go |
Implements listing and version ordering. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/download_wire_test.go |
Tests download HTTP behavior. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/download_test.go |
Tests blob selection logic. |
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/bom_test.go |
Tests BOM and file handling. |
cli/azd/extensions/azure.ai.dataset/internal/messages/messages.go |
Centralizes user-facing messages. |
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/validator.go |
Validates Foundry endpoints. |
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/types.go |
Defines endpoint-resolution types. |
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/store.go |
Reads shared project context. |
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/resolver.go |
Implements endpoint precedence. |
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/resolver_test.go |
Tests endpoint resolution. |
cli/azd/extensions/azure.ai.dataset/internal/exterrors/errors.go |
Adds structured error helpers. |
cli/azd/extensions/azure.ai.dataset/internal/exterrors/codes.go |
Defines structured error codes. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/table_test.go |
Tests table rendering. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/surface_test.go |
Tests command surface and flags. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/root.go |
Builds the command tree. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/output.go |
Implements JSON and text output. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/output_test.go |
Tests output formatting. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/names.go |
Validates dataset names. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/names_test.go |
Tests naming constraints. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/helpers_test.go |
Tests command helpers. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/debug.go |
Configures debug logging. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go |
Implements dataset commands. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/context.go |
Creates command service context. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/artifacts.go |
Defines persistence helpers. |
cli/azd/extensions/azure.ai.dataset/internal/cmd/apiversions.go |
Defines service API versions. |
cli/azd/extensions/azure.ai.dataset/go.mod |
Defines the Go module. |
cli/azd/extensions/azure.ai.dataset/extension.yaml |
Declares extension metadata. |
cli/azd/extensions/azure.ai.dataset/cspell.yaml |
Configures spell checking. |
cli/azd/extensions/azure.ai.dataset/ci-test.ps1 |
Adds CI test execution. |
cli/azd/extensions/azure.ai.dataset/ci-build.ps1 |
Adds CI binary builds. |
cli/azd/extensions/azure.ai.dataset/CHANGELOG.md |
Records the initial release. |
cli/azd/extensions/azure.ai.dataset/build.sh |
Adds Unix cross-platform builds. |
cli/azd/extensions/azure.ai.dataset/build.ps1 |
Adds Windows cross-platform builds. |
cli/azd/extensions/azure.ai.dataset/.golangci.yaml |
Configures Go linting. |
cli/azd/extensions/azure.ai.dataset/.gitignore |
Ignores generated artifacts. |
.github/workflows/lint-ext-azure-ai-dataset.yml |
Adds GitHub lint workflow. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| "-X 'azureaieval/internal/version.Version=$Version' " + | ||
| "-X 'azureaieval/internal/version.Commit=$SourceVersion' " + | ||
| "-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' " |
| ) | ||
| fi | ||
|
|
||
| VERSION_PATH="azureaieval/internal/version" |
| ) | ||
| } | ||
|
|
||
| $VERSION_PATH = "azureaieval/internal/version" |
| q.Set("comp", "list") | ||
| u.RawQuery = q.Encode() | ||
|
|
||
| log.Printf("[dataset_api] listing blobs: %s", u.Redacted()) |
| func DatasetVersionNotFoundWithHint(dataset, version string) error { | ||
| return fmt.Errorf( | ||
| "no dataset %q at version %q in this project; "+ | ||
| "`azd ai dataset list` shows the ones there are", dataset, version) | ||
| } |
| var respErr *azcore.ResponseError | ||
| if !errors.As(err, &respErr) { |
| var respErr *azcore.ResponseError | ||
| if errors.As(err, &respErr) { |
| cmd.Flags().StringVar(&version, "version", "", | ||
| "Current version to increment from. Omit to increment from the latest registered version.") |
| - The CRUD groups moved here from `azure.ai.evaluations`. Generation stayed | ||
| there: `dataset generate` writes the `datasets:` entry in `evals/eval.yaml`, | ||
| and that file belongs to the evaluation extension. |
Two lines of ci-test.ps1 assumed PowerShell 6 or later. $IsWindows does not exist on Windows PowerShell 5.1, so the .exe suffix was never appended; and Join-Path there takes a single child path, so the three-argument call failed outright. The script then found no gotestsum, printed the fallback notice, and ran bare `go test` with no JUnit report -- while still exiting 0, which is why it went unnoticed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 53 out of 54 changed files in this pull request and generated no new comments.
Suppressed comments (19)
cli/azd/extensions/azure.ai.dataset/README.md:28
- This points users to a command that the evaluation extension does not expose. Its surface is
azd ai eval generate, with--datasetselecting dataset generation; using the current text results in an unknown-command error.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
cli/azd/extensions/azure.ai.dataset/build.sh:36
- The Unix extension build stamps
azureaieval, which is not this module. Point the linker atazureaidataset/internal/versionso locally packed binaries carry the requested version and build identity.
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- The debug logger writes
azd-ai-dataset-*.log, but this rule ignores the old evaluation filename. Dataset logs—including request diagnostics—will remain unignored and can be committed accidentally.
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/ci-test.ps1:10
- This names the evaluation extension's opt-in variable, but
tests/cli/TestMainchecksAZURE_AI_DATASET_E2E_LIVE. Following this instruction silently skips the live suite instead of running it.
# AZURE_AI_EVAL_E2E_LIVE and a project endpoint. They are still type-checked
cli/azd/extensions/azure.ai.dataset/README.md:41
- The documented endpoint cascade omits the
AZURE_AI_PROJECT_ENDPOINTfallback and the shared global project context thatprojectctx.Resolvechecks before host variables. Users may unnecessarily set a lower-precedence value because this order does not match runtime behavior.
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45
- These linker symbols still target the evaluation module. The release template invokes this script for every artifact, so
azureaidataset/internal/version.Version,Commit, andBuildDateare never stamped and the dataset User-Agent remains at itsdevdefault.
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- The PowerShell extension build stamps
azureaieval, which is not this module. Point the linker atazureaidataset/internal/versionso locally packed binaries carry the requested version and build identity.
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:459
url.URL.Redacted()only hides user-info passwords; it does not remove query parameters. This logs the full container SAS, includingsig, into the debug file. Log only the scheme/host/path and keep the authenticated URL solely on the request.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/list.go:19- Both list methods discard
NextLink, so projects with a paged response silently lose datasets/versions. This also makesshowand update-version discovery select an older version. Follow all pages and validate each continuation URL is same-origin before requesting it.
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:79 - A list failure is currently converted into
exists == false.updatethen reports that the dataset does not exist for authentication, transport, and service failures, whilecreateproceeds toward an upload. Propagate the list error; only a successful empty response means absent.
existing, err := ec.datasetClient.ListDatasetVersions(
ctx, name, ProjectEndpointAPIVersion,
)
exists := err == nil && existing != nil && len(existing.Value) > 0
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:88
- The consistency probes ignore every error as if the version were absent. If either point read fails with auth, transport, or a 5xx response,
updatereturns the misleading “does not exist” error. Continue only on 404 and propagate other failures.
if _, err := ec.datasetClient.GetDataset(
ctx, name, v, ProjectEndpointAPIVersion,
); err == nil {
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:105
- The PR states that CRUD needs no configuration, but every create/update mutates the active environment by writing one global
EVAL_DATASET_VERSION. That key cannot represent multiple datasets and has no reader in this extension, so the command introduces an unrelated persistent side effect. Remove this write and its dead cache helpers.
if err := ec.setEnvValue(ctx, envKeyDatasetVersion, ds.Version); err != nil {
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:157
- This collapses every listing error into “no registered version” and then attempts to publish 1.0. A transient list failure can therefore create an old version or turn the real service error into a conflict. Return the error separately; reserve the empty value for a successful empty listing.
cli/azd/extensions/azure.ai.dataset/internal/exterrors/errors.go:94 - Use
errors.AsType[*azcore.ResponseError](err)here. This repository's Go 1.26 modernization rules prohibit the target-variableerrors.Aspattern, and CI runsgo fix -diff.
var respErr *azcore.ResponseError
if errors.As(err, &respErr) {
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:177
- Use the Go 1.26
errors.AsTypeform required by this repository; the current target-variable pattern is subject to the enforcedgo fixmodernization check.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:168 - This module targets Go 1.26, and the repository requires
errors.AsTypefor typed error matching. Thego fix -diffworkflow will reject this older target-variable pattern.
cli/azd/extensions/azure.ai.dataset/README.md:6 - This quickstart cannot currently succeed: the same README notes that no registry entry exists, so
azd extension install azure.ai.datasetcannot resolve. Document the local pack/publish flow until the registry entry lands, or add the registry entry before presenting this command.
$ azd extension install azure.ai.dataset
cli/azd/extensions/azure.ai.dataset/README.md:22
- The documented delete command omits the required
--versionflag, so copying it always fails with--version is required. Include the mandatory flag in the command table.
| `azd ai dataset delete <name>` | Delete a dataset version |
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:304
- The code already documents that this version listing can return empty for a few seconds after creation, but
showtreats that stale response as proof the dataset does not exist. An immediatecreate→showcan therefore fail falsely; use a bounded consistency fallback before returning not-found.
if len(list.Value) == 0 {
return messages.DatasetNotFound(name)
}
cspell: - Correct "neighbouring" to "neighboring" in the changelog. - Add the identifiers cspell cannot infer to the extension dictionary: azureaidataset, projectctx, projectctx's, exterrors, CLI's. These mirror the entries the azure.ai.evaluations extension already carries. golangci-lint: - Delete datasetContext.getEnvValue and the surface_test find helper. Both had no callers, so they were dead rather than suppressible. - Rename the storageServer test field credentialPath to uriPath. It holds a URL path, never a secret; the old name is what tripped gosec G101, so the rename removes the finding instead of hiding it behind a nolint. - Annotate the three filepath.WalkDir ReadFile calls in surface_test with //nolint:gosec and the reason: these walk this package's own source, not caller input. No behaviour change. go build, go vet, gofmt and go test ./... all clean.
Seven review comments flagged this and were all marked outdated as line numbers moved, so none of them had actually been applied. `url.URL.Redacted()` masks a userinfo password only; it leaves the query string intact. A storage SAS carries its credential in the query as `sig`, so both `log.Printf(... u.Redacted())` sites wrote a live, still-valid storage credential into `azd-ai-dataset-*.log` whenever --debug was on. Separately, `http.Client.Do` returns a *url.Error whose message embeds the full request URL. For the three SAS-backed calls (blob upload, container listing, blob download) a DNS/TLS/timeout/cancellation failure therefore printed the SAS to the user. - Add logSafeURL, which drops the query and fragment before formatting. - Add redactURLError, which rebuilds a *url.Error with a redacted URL and without mutating the original. Uses errors.AsType per cli/azd/AGENTS.md. - Apply to both log sites and all three SAS-backed Do() calls. Tests assert the premise as well as the fix: one case pins that Redacted() alone does leak the signature, so the unsafe call cannot quietly come back. The request itself still uses the unmodified URL. gofmt, go vet, go build and go test ./... are clean.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 54 out of 55 changed files in this pull request and generated 3 comments.
Suppressed comments (20)
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:427
- A failed direct download returns a raw
*url.Error, which includes the full SAS-bearing request URL and can expose the token in user-facing output or the fallback debug log. Redact the query before wrapping this error. — azd-code-reviewer
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:469 - A container-list transport error includes the full SAS URL in
*url.Error.Error(), so returning it throughListingContainerBlobscan expose the token. Sanitize the query before wrapping the error. — azd-code-reviewer
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:507 - A failed blob download returns a raw
*url.Errorcontaining the full SAS-bearing URL. That error is rendered to users, so redact its query before wrapping it. — azd-code-reviewer
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/list.go:19 NextLinkis captured but never followed, solist,versions list, latest-version resolution, and existence checks only see the first page. The methods promise all datasets/versions and the JSON renderer deliberately drops the paging link, leaving callers no way to fetch omitted pages. Follow pages inside the client before returning. — azd-code-reviewer
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:105- Every dataset publish overwrites the same project-wide
EVAL_DATASET_VERSION, even though the command can manage many independently named datasets. Publishing dataset B can therefore leave consumers treating B's version as A's, and this extension never reads the key back. Remove this mutation or persist a dataset-keyed value with a documented consumer contract. — azd-code-reviewer
if err := ec.setEnvValue(ctx, envKeyDatasetVersion, ds.Version); err != nil {
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45
- These linker targets still use the evaluation module path. Because this module is
azureaidataset, the release build leavesVersion,Commit, andBuildDateat their defaults, including thedevversion used in the User-Agent. Point all three targets at this module. — azd-code-reviewer
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/build.sh:36
- This copied module path prevents local cross-platform builds from stamping the dataset extension's version metadata; the linker target must match the
azureaidatasetmodule. — azd-code-reviewer
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- This copied module path prevents the PowerShell build from stamping this extension's version metadata; target the
azureaidatasetmodule instead. — azd-code-reviewer
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:96
- This turns every list failure—401/403, timeout, or 5xx—into
exists == false, then also ignores non-404 point-read failures.updateconsequently reports “does not exist,” whilecreatemay attempt an upload without establishing that the name is free. Return list errors and only ignore a point-read error when it is actually not-found. — azd-code-reviewer
exists := err == nil && existing != nil && len(existing.Value) > 0
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:304
- This treats an empty version list as authoritative even though the new upload path explicitly documents that this listing is eventually consistent. A
showimmediately after a successfulcreatecan therefore report that the dataset does not exist. Resolve the just-published version through a consistent point read or retry the empty listing before returning not-found. — azd-code-reviewer
if len(list.Value) == 0 {
return messages.DatasetNotFound(name)
}
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- The generated debug file is named
azd-ai-dataset-<date>.log(internal/cmd/debug.go:31), so this copied evaluation pattern does not ignore it and makes credential-bearing diagnostics easy to commit accidentally. — azd-code-reviewer
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/.golangci.yaml:12
- This disables the repository's established 125-character Go limit for the new module; the root configuration sets it at
cli/azd/.golangci.yaml:27. Keeping 220 here means this extension's dedicated lint workflow accepts code the rest of azd rejects. — azd-code-reviewer
line-length: 220
cli/azd/extensions/azure.ai.dataset/ci-test.ps1:10
- The live harness actually requires
AZURE_AI_DATASET_E2E_LIVE; this copied evaluation variable sends anyone following the CI instructions to a run that silently skips all tagged tests. — azd-code-reviewer
# AZURE_AI_EVAL_E2E_LIVE and a project endpoint. They are still type-checked
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:366
- The live test and stated behavior require delete to be idempotent, but a service that represents an already-absent version as 404 is converted into a failure here. Treat not-found as successful deletion and only return other errors. — azd-code-reviewer
if dataset_api.IsNotFound(err) {
return messages.DatasetVersionNotFound(name, version)
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:238
- This now makes an unknown name return an empty successful list, while the PR description still says this extension errors and presents the behavior as an open question. Update the description to record the decision implemented and tested here. — azd-code-reviewer
// An unknown name lists nothing and succeeds; it is not an error.
// `-o json` callers range over the array, and a delete is checked
// for idempotence by listing what is left. The empty sentence names
// the dataset, though: the project may hold plenty of others, so
// "No datasets found." would be answering a different question.
return renderDatasets(cmd, list, messages.NoDatasetVersions(name))
cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:51
- Cleanup silently returns on list/JSON failures and ignores every delete result, so the live suite can pass while leaving all created dataset versions behind. Make cleanup failures visible and require each delete to succeed. — azd-code-reviewer
cli/azd/extensions/azure.ai.dataset/README.md:6 - This first-run command cannot work in this PR because the registry entry is deliberately omitted, as the TODO at lines 54–57 confirms. Replace it with complete local-registry setup instructions or defer the install example until the extension is resolvable. — azd-code-reviewer
$ azd extension install azure.ai.dataset
cli/azd/extensions/azure.ai.dataset/README.md:22
deleterejects calls without--version, but the command table presents this incomplete invocation. Include the required flag so the documented command succeeds. — azd-code-reviewer
| `azd ai dataset delete <name>` | Delete a dataset version |
cli/azd/extensions/azure.ai.dataset/README.md:41
- This claims a three-level endpoint cascade, but the resolver also checks
AZURE_AI_PROJECT_ENDPOINTat the azd/host levels and the shared global project context between them. Document all levels so users can understand why an endpoint other than these three wins. — azd-code-reviewer
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/README.md:28
- The companion evaluation extension exposes
azd ai eval generate, notazd ai eval dataset generate, and its current configuration isazure.eval.yaml. As written, this sends users to a nonexistent command and names the wrong file. — azd-code-reviewer
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
| resp, err := httpClient.Do(req) | ||
| if err != nil { | ||
| return messages.UploadingBlobFailed(err) |
| q.Set("comp", "list") | ||
| u.RawQuery = q.Encode() | ||
|
|
||
| log.Printf("[dataset_api] listing blobs: %s", u.Redacted()) |
| best := "" | ||
| bestOrder := -2.0 | ||
| for _, d := range datasets { | ||
| if o := VersionOrder(d.Version); o > bestOrder { | ||
| bestOrder, best = o, d.Version | ||
| } | ||
| } | ||
| if best == "" && len(datasets) > 0 { | ||
| return datasets[len(datasets)-1].Version | ||
| } |
The cspell job runs `--config ./.vscode/cspell.yaml`, so words have to be known to the repo-level config; the extension's own cspell.yaml serves the other job and the editor. - "unauthorised" -> "unauthorized" in a ServiceRefused comment. US spelling is what the rest of the repo uses. - Rename the redactURLError local from `uerr` to `urlError`. `uerr` is not a word cspell can resolve, and naming it properly removes the finding rather than adding a dictionary entry for an abbreviation. - Register CODEOWNERS for the extension README under `overrides`, matching how this config already scopes per-file words. Verified with the exact CI invocation: 577 files checked, 0 issues.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 55 out of 56 changed files in this pull request and generated 1 comment.
Suppressed comments (16)
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/list.go:24
- [azd-code-reviewer] Both list methods return only the first page even though
DatasetListexposesNextLink. Projects with enough datasets or versions will omit entries;showcan select the wrong latest version andupdatecan derive a conflicting version. Follow same-origin pagination links until exhausted, as the repository's connection clients do.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:157 - [azd-code-reviewer]
latestRegisteredVersioncollapses every list error into an empty version. On an auth or service failure,UploadNextVersionthen attempts1.0and can return a misleading conflict instead of the original failure. Return(string, error)and propagate errors from both call sites; an empty successful list already represents an unknown or not-yet-consistent dataset.
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:105 - [azd-code-reviewer] CRUD is described as configuration-free, but every create/update writes one global
EVAL_DATASET_VERSIONinto the active azd environment. That value has no dataset name, and--project-endpointmay target a project unrelated to the active environment, so publishing any dataset can overwrite evaluation state with an ambiguous or foreign version. Remove this persistence from the standalone dataset extension.
if err := ec.setEnvValue(ctx, envKeyDatasetVersion, ds.Version); err != nil {
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:194
- [azd-code-reviewer] This module targets Go 1.26, and the repository requires
errors.AsTyperather than the mutable-targeterrors.Aspattern. Use the typed helper here.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:203 - [azd-code-reviewer] Use the repository's Go 1.26
errors.AsTypepattern here instead of declaring a mutable target forerrors.As.
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45 - [azd-code-reviewer] The release build stamps symbols in the evaluation module, which is not part of this binary. The linker therefore leaves
version.Versionasdev, so every published request still usesazd-ext-azure-ai-dataset/devdespite the identity fix. Stamp theazureaidatasetmodule instead.
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:93
- [azd-code-reviewer] Any listing failure is currently treated as “dataset absent.” A 401, 403, timeout, or 5xx therefore makes
updatereport that the dataset does not exist and letscreatecontinue toward an upload. Propagate list errors, and ignore onlyIsNotFoundfrom the consistency probes.
exists := err == nil && existing != nil && len(existing.Value) > 0
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- [azd-code-reviewer] This local PowerShell build also targets the evaluation module, so its version, commit, and date linker values are silently ignored. Use this extension's module path.
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/build.sh:36
- [azd-code-reviewer] This local shell build targets the evaluation module, leaving the dataset binary's build metadata at its development defaults. Use this extension's module path.
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/README.md:41
- [azd-code-reviewer] The documented cascade omits two implemented sources: the shared project context and the
AZURE_AI_PROJECT_ENDPOINTfallback. This makes the stated precedence inaccurate and hides why a command may use an endpoint other than the listed values.
Every command resolves the Foundry project endpoint in this order:
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/README.md:29
- [azd-code-reviewer] This points readers to a command that the companion evaluation extension does not expose (
#9500definesazd ai eval generate) and names the obsolete config path. A user following this section gets an unknown command before reaching dataset creation.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
extension's file. Splitting the two would leave a generated dataset registered
cli/azd/extensions/azure.ai.dataset/ci-test.ps1:10
- [azd-code-reviewer] The live harness checks
AZURE_AI_DATASET_E2E_LIVE, not this evaluation variable. Following this CI documentation causes the suite to print its skip message and exit successfully instead of running.
# AZURE_AI_EVAL_E2E_LIVE and a project endpoint. They are still type-checked
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- [azd-code-reviewer] Debug logging now writes
azd-ai-dataset-*.log, so this inherited evaluation pattern does not ignore the generated files. Running with--debugleaves untracked logs in the extension directory.
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/internal/exterrors/errors.go:95
- [azd-code-reviewer] Use
errors.AsType[*azcore.ResponseError]here; the repository's Go 1.26 modernization rules prohibit the mutable-targeterrors.Asform.
cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:51 - [azd-code-reviewer] Cleanup silently returns on list/JSON failures and ignores every delete result, so a green live run can leave dataset versions in the shared Foundry project. Make cleanup failures fail the test and show the command output instead of hiding leaked remote state.
cli/azd/extensions/azure.ai.dataset/CHANGELOG.md:11 - [azd-code-reviewer] The companion extension exposes
azd ai eval generate, notdataset generate. The release notes currently document a command users cannot run.
- The CRUD groups moved here from `azure.ai.evaluations`. Generation stayed
there: `dataset generate` writes the `datasets:` entry in `evals/eval.yaml`,
and that file belongs to the evaluation extension.
| httpClient := blobHTTPClient() | ||
| resp, err := httpClient.Do(req.Raw()) | ||
| if err != nil { | ||
| return nil, messages.DownloadingDatasetBlob(err) |
Three review findings, all still live in the code despite being marked outdated when line numbers moved. latestRegisteredVersion swallowed every listing error and returned "", which is indistinguishable from a brand-new dataset. A 403, throttle, timeout or cancellation therefore restarted an existing dataset at 1.0 and issued a pending upload against it. It now returns (string, error): a 404 and an empty listing still report "" with no error, everything else is propagated. The retry loop keeps best-effort semantics, because the version it just had refused is already a correct next step. LatestVersion documented a fallback to the service's last entry when nothing is orderable, but bestOrder started at -2.0 and VersionOrder returns -1 for an unorderable version, so the *first* one always won and the fallback was unreachable. Starting at -1.0 makes the documented behaviour real. Also finishes the errors.As -> errors.AsType migration required by cli/azd/AGENTS.md; no errors.As calls remain in the extension. Tests cover all three, and were checked by reverting each fix: - a refused listing must surface and must not reach startPendingUpload - an unknown dataset (404) still starts at 1.0 - an all-unorderable list returns the last entry, not the first
The paged response modelled nextLink but neither list method followed it, so a project with more than one page listed incompletely and latestRegisteredVersion could pick a version from a stale first page. followPages walks the links and returns one merged list. Two guards come with it: - Same-origin check. nextLink is service-supplied and the pipeline attaches the caller's token, so a link off the endpoint's origin is refused rather than followed; otherwise a spoofed or redirected response could hand that token to another host. - Repeat and count cap. A service that keeps returning the same link stops the walk instead of spinning; that is the service misbehaving, not a reason to fail the user's command. Also corrects the envKeyDatasetVersion comment. It claimed the key caches a version "so a later read does not have to list every version" — no code in either extension reads it, so the comment described an optimization that does not exist. Tests: pages merge in order, an off-origin link fails the call and the other host is never contacted, and a repeated link terminates.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 57 out of 58 changed files in this pull request and generated 1 comment.
Suppressed comments (17)
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/list.go:19
- Both list methods stop after the first response even though this model exposes
NextLink. In a paged project,listis incomplete andLatestVersioncan select an old version, causingupdateto target the wrong version. Follow all continuation links and validate that each link has the same origin as the configured endpoint.
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:93 - The list error is converted into
exists == false. Forupdate, a 401, 403, or 5xx is then swallowed along with both probe errors, and the user receives “does not exist” instead of the authentication/service failure. Propagate non-404 failures from both the listing and point probes.
existing, err := ec.datasetClient.ListDatasetVersions(
ctx, name, ProjectEndpointAPIVersion,
)
exists := err == nil && existing != nil && len(existing.Value) > 0
if !exists {
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:232
- An unknown dataset can be represented by a 404—the same client already handles that case in
latestRegisteredVersion. Returning every error here contradicts the documented/tested contract thatversions list <unknown>succeeds with an empty result.
list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
if err != nil {
return messages.ListingDatasetVersions(name, err)
}
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:298
ListDatasetVersionsis documented elsewhere in this client as eventually consistent, butshowreads it only once. Immediately after a successful create or update, an empty stale page therefore becomes a false “dataset not found” error. Add a bounded retry/backoff for an empty latest-version listing before concluding the dataset is absent.
if version == "" {
list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
if err != nil {
return messages.ResolvingLatestDatasetVersion(name, err)
}
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:366
- The command converts a 404 into failure even though the CLI contract and
TestCLIDeleteIsIdempotentrequire deleting an absent version to succeed. Ignore not-found responses and emit the normal idempotent success result; only other failures should be returned.
); err != nil {
if dataset_api.IsNotFound(err) {
return messages.DatasetVersionNotFound(name, version)
}
return messages.DeletingDatasetVersion(name, version, err)
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/models.go:51
- These wire-format aliases are also serialized directly by every
-o jsoncommand. A response using snake_case therefore exposesdata_uri, while the same dataset from another endpoint exposesdataUri(and a mixed response can expose both), breaking the stable script-facing shape asserted by the CLI tests. Decode aliases internally, then map to a dedicated canonical output model before rendering JSON.
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45 - The release pipeline invokes this script, but these linker paths still target the evaluation module. The dataset module’s version variables are never stamped, so released requests keep the
azd-ext-azure-ai-dataset/devUser-Agent instead of the published version.
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/build.sh:36
- This copied linker path targets
azureaieval, so locally packaged binaries do not stamp this module’sVersion,Commit, orBuildDate. Point it at the module declared by this extension.
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- This copied linker path targets
azureaieval, so locally packaged binaries do not stamp this module’sVersion,Commit, orBuildDate. Point it at the module declared by this extension.
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/README.md:6
- This quickstart cannot currently work: the same README states that no registry entry exists, so
azd extension install azure.ai.datasetcannot resolve. Document the local pack/publish installation path here, or defer this install example until the registry entry lands.
$ azd extension install azure.ai.dataset
cli/azd/extensions/azure.ai.dataset/README.md:22
- The documented delete invocation omits the required
--versionflag, so copying it always fails with “--version is required.”
| `azd ai dataset delete <name>` | Delete a dataset version |
cli/azd/extensions/azure.ai.dataset/README.md:28
- The companion evaluation surface exposes
azd ai eval generate, notazd ai eval dataset generate, and its default configuration isazure.eval.yaml(with compatibility for an existingeval.yaml), notevals/eval.yaml. These instructions currently lead users to a nonexistent command and file.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
cli/azd/extensions/azure.ai.dataset/README.md:41
- This list omits two implemented resolution sources and the
AZURE_AI_PROJECT_ENDPOINTfallback, so users may not understand why a different endpoint won. Document the full cascade implemented byprojectctx.Resolve.
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- Debug logging creates
azd-ai-dataset-<date>.log, but this copied pattern ignores only evaluation logs. Dataset debug logs will therefore appear as untracked files and can be committed accidentally.
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:51
- Cleanup silently returns when listing or JSON decoding fails, and delete failures are also ignored. A live test can therefore pass while leaving every created dataset version in the shared Foundry project. Fail/report these operations so leaked test assets are visible.
cli/azd/extensions/azure.ai.dataset/internal/messages/messages.go:195 - This error is specifically about a missing version, but it sends the user to
dataset list, which does not enumerate that dataset’s versions. Point todataset versions list <name>so the suggested command can actually find a valid version.
cli/azd/extensions/azure.ai.dataset/ci-test.ps1:10 - The live suite is gated by
AZURE_AI_DATASET_E2E_LIVE, not the copied evaluation variable named here. Anyone following this CI documentation will set the wrong variable and the suite will exit without running.
# AZURE_AI_EVAL_E2E_LIVE and a project endpoint. They are still type-checked
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 57 out of 58 changed files in this pull request and generated 1 comment.
Suppressed comments (12)
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45
- These linker overrides still target the evaluation module, so the release pipeline silently leaves this binary's
Version,Commit, andBuildDateatdev/none/unknown. Stampazureaidataset/internal/versioninstead so the dataset User-Agent carries the released identity.
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/build.sh:36
- The local shell build stamps
azureaieval, which is not this module, so dataset binaries keep the fallback version metadata. Point the linker path at this module.
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- The local PowerShell build stamps
azureaieval, which is not this module, so dataset binaries keep the fallback version metadata. Point the linker path at this module.
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- Debug logging creates
azd-ai-dataset-<date>.log, but this copied pattern ignores eval logs instead. Running the new extension with--debugtherefore leaves its credential-sensitive diagnostic log visible as an untracked file.
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/README.md:28
- Both references are stale relative to the companion evaluation extension: its command surface is
azd ai eval generate, and its canonical configuration isazure.eval.yaml(while preserving an existingeval.yaml), notevals/eval.yaml. As written, users are sent to a nonexistent command and file.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
cli/azd/extensions/azure.ai.dataset/README.md:41
- This omits two resolution levels implemented by
projectctx.Resolve: the shared global project context andAZURE_AI_PROJECT_ENDPOINTfallbacks in both azd and host environments. Document the full precedence order; otherwise users can neither predict which endpoint wins nor discover the supported fallback.
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go:515
- This direct-download path wraps the raw
*url.Error, whose URL contains the SASsigquery. On a transport failure the credential can therefore appear both in the user-facing error and in the debug log at the fallback call site. Apply the sameredactURLErrorused by the other blob operations before wrapping it.
cli/azd/extensions/azure.ai.dataset/README.md:6 - This quick start cannot currently work: the PR deliberately omits the
registry.jsonentry, and lines 54-57 state thatazd extension install azure.ai.datasetcannot resolve yet. Replace this with the local pack/publish workflow for now, or add the install command only when the registry entry lands.
$ azd extension install azure.ai.dataset
cli/azd/extensions/azure.ai.dataset/README.md:22
- The documented delete invocation omits
--version, but the implementation rejects every delete without that flag. Include the required flag so copying this command does not immediately fail.
| `azd ai dataset delete <name>` | Delete a dataset version |
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:26
- This probe does not cover every first version this CLI can publish. Because
createalso exposes--version,create --version 4.0publishes5.0; during listing lag, the subsequent update probes only1.0and1and incorrectly reports that the dataset does not exist. Either remove--versionfrom create or includeNextVersion(version)in the fallback probes.
var firstDatasetVersions = []string{dataset_api.NextVersion(""), "1"}
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:93
- A listing failure is currently converted into
exists == false, and every direct-probe error is also ignored. A 403, timeout, or token failure therefore becomes the misleading “does not exist” result for update, while create may continue toward an upload. Only 404 should mean absence; propagate other errors.
existing, err := ec.datasetClient.ListDatasetVersions(
ctx, name, ProjectEndpointAPIVersion,
)
exists := err == nil && existing != nil && len(existing.Value) > 0
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:232
- The comment promises that an unknown name returns an empty successful list, but a 404 from the versions endpoint is returned as an error here. The API client already treats 404 as the normal “no versions” case in
latestRegisteredVersion; normalize it here too so the command's documented behavior is consistent across service responses.
list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
if err != nil {
return messages.ListingDatasetVersions(name, err)
}
Pagination, from the review of the nextLink commit: - A relative nextLink was refused. url.Parse leaves scheme and host empty, so the same-origin check rejected a legitimate link with a message naming no host, turning a working listing into a hard failure. The link is now resolved against the endpoint first, which keeps the origin guarantee intact. - A page answering 200 with no body failed the whole listing and discarded every page already collected. An empty page now ends the walk. - The page cap and repeat-link exits truncated silently. They now log, because a short list that nobody knows is short is how a stale latest-version gets chosen. - followPages appended into the first page's backing array, which the caller still owns. It copies now. Changelog: this is the extension's first release, so both changelogs describe what it does rather than accumulating fixes from our own pre-release cycles. --fail-on help now states the exit code a caller observes (1) without explaining why it is not the one the process exits with.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 58 out of 59 changed files in this pull request and generated no new comments.
Suppressed comments (13)
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:89
- [azd-code-reviewer] The point-read probe also treats every error as “not found.” If authentication expires or the service fails after an empty listing,
updatereports that the asset is absent. Ignore only 404 responses and surface other probe failures.
for _, v := range firstDatasetVersions {
if _, err := ec.datasetClient.GetDataset(
ctx, name, v, ProjectEndpointAPIVersion,
); err == nil {
exists = true
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45
- [azd-code-reviewer] These linker targets still use the evaluation module path. The release pipeline invokes this script, so the assignments miss
azureaidataset/internal/version.Versionand every published binary sendsazd-ext-azure-ai-dataset/devinstead of its release version. Stamp the dataset module path here.
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/build.sh:36
- [azd-code-reviewer] The local cross-platform build also stamps the copied evaluation module path, so these binaries retain the
devuser-agent version. PointVERSION_PATHat this module.
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- [azd-code-reviewer] This PowerShell build stamps
azureaieval, not the dataset module, leavingversion.Versionatdev. Use the module path declared by this extension.
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:80
- [azd-code-reviewer] A listing failure is currently converted into
exists == false. In particular,updateturns a 403, timeout, or service failure into the misleading “dataset does not exist” error instead of preserving the real failure. Only a 404/empty result should enter the consistency probe.
This issue also appears on line 85 of the same file.
existing, err := ec.datasetClient.ListDatasetVersions(
ctx, name, ProjectEndpointAPIVersion,
)
exists := err == nil && existing != nil && len(existing.Value) > 0
if !exists {
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:366
- [azd-code-reviewer] This contradicts the new idempotent-delete contract and its live test: whenever the service returns 404 rather than 204 for an absent version, cleanup fails. Treat not-found as successful deletion and only wrap other failures.
if err := ec.datasetClient.DeleteDatasetVersion(
ctx, name, version, ProjectEndpointAPIVersion,
); err != nil {
if dataset_api.IsNotFound(err) {
return messages.DatasetVersionNotFound(name, version)
cli/azd/extensions/azure.ai.dataset/internal/cmd/root.go:52
- [azd-code-reviewer] None of these runnable commands registers SDK flag options for inherited
--output. As a result, arbitrary values such as-o yamlsucceed and silently render a table, while generated metadata cannot advertise the supported values. Registerjson/tablewith atabledefault on each runnable command, followingextensions/azure.ai.skills/internal/cmd/skill_list.go:51.
rootCmd.AddCommand(
newDatasetCreateCommand(),
newDatasetUpdateCommand(),
newDatasetListCommand(),
newDatasetShowCommand(),
newDatasetDeleteCommand(),
newDatasetVersionsCommand(),
)
cli/azd/extensions/azure.ai.dataset/internal/messages/messages.go:358
- [azd-code-reviewer] This helper is also called when
--no-promptwas not supplied, sodataset create <name>anddataset delete <name>falsely tell users they are running non-interactively. Since these commands do not prompt in either mode, report only that the flag is required.
cli/azd/extensions/azure.ai.dataset/README.md:31 - [azd-code-reviewer] The companion extension exposes
azd ai eval generate, notazd ai eval dataset generate, so this onboarding command fails with “unknown command.” The companion PR also namesazure.eval.yaml; avoid pinning the staleevals/eval.yamlpath here.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
extension's file. Splitting the two would leave a generated dataset registered
with the service but absent from the configuration, so `azd up` would not
reconcile it and no eval could name it.
cli/azd/extensions/azure.ai.dataset/README.md:41
- [azd-code-reviewer] This documented cascade omits two implemented sources and therefore gives the wrong precedence. Global project context is checked before host variables, and
AZURE_AI_PROJECT_ENDPOINTis a fallback at both environment levels.
Every command resolves the Foundry project endpoint in this order:
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- [azd-code-reviewer] Debug mode creates
azd-ai-dataset-<date>.log, but this copied pattern ignores eval logs instead. Dataset debug logs will remain untracked files in the extension directory.
# Debug log written when --debug or AZD_EXT_DEBUG is set.
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:51
- [azd-code-reviewer] Cleanup silently returns when listing or JSON decoding fails, and it also ignores each delete result. A transient cleanup failure therefore leaves live Foundry datasets behind while the suite still passes. Assert every cleanup operation so leaked state is reported.
cli/azd/extensions/azure.ai.dataset/CHANGELOG.md:17 - [azd-code-reviewer] No CLI command calls
DownloadDatasetContent; only internal tests do. This release note therefore advertises a content-read capability users cannot access. Remove the bullet or add the corresponding public command before release.
- Reads dataset content back, whether the service hands out a blob URI or the
container holding it.
The feed still serves beta.5, which predates the SAS redaction, the version-selection error propagation and pagination. Bumping so a bug bash tester can tell which build they have.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 58 out of 59 changed files in this pull request and generated no new comments.
Suppressed comments (15)
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45
- These linker targets still use the evaluation module path, so the release pipeline's
-Xflags do not stamp this extension'sVersion,Commit, orBuildDate. In particular, the dataset User-Agent continues to reportdev, defeating the identity/versioning change. Targetazureaidataset/internal/versioninstead.
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- The local PowerShell build also points its linker stamps at the evaluation module, so locally packaged dataset binaries retain the default
dev/none/unknownvalues. Use this module's import path.
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/build.sh:36
- The shell build stamps
azureaieval, which is not this module, so its generated binaries do not receive the dataset version metadata. Use the dataset module path.
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/README.md:28
- Both references are stale relative to the companion evaluation extension: its surface is
azd ai eval generate, and its canonical configuration isazure.eval.yaml, not aneval dataset generatecommand writingevals/eval.yaml. As written, the documented command does not exist.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
cli/azd/extensions/azure.ai.dataset/README.md:41
- This advertised resolution order omits two implemented sources: the shared project context is checked before host variables, and
AZURE_AI_PROJECT_ENDPOINTis a fallback at both environment levels. Documenting only three levels makes endpoint selection surprising when the omitted higher-precedence value wins.
Every command resolves the Foundry project endpoint in this order:
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- Debug logging now writes
azd-ai-dataset-*.log, but this copied evaluation pattern no longer matches it. Running with--debugfrom the extension directory leaves generated logs unignored.
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:80
- A non-404 listing failure is discarded here. For example, a 403 or timeout makes
updatereport that the dataset does not exist, whilecreateproceeds toward an upload without establishing that the name is free. Only not-found/empty results should enter the eventual-consistency probe; propagate other listing failures.
existing, err := ec.datasetClient.ListDatasetVersions(
ctx, name, ProjectEndpointAPIVersion,
)
exists := err == nil && existing != nil && len(existing.Value) > 0
if !exists {
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:299
- This treats the eventually consistent version listing as authoritative even though the upload path explicitly handles it lagging after a publish. Immediately after
create,showcan incorrectly return “no dataset”; immediately afterupdate, it can show the previous version as latest. Resolve latest with point reads past the listed version (bounded, as the upload conflict walk is) before rendering or declaring the dataset absent.
list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
if err != nil {
return messages.ResolvingLatestDatasetVersion(name, err)
}
// The service answers an unknown name with an empty list rather
cli/azd/extensions/azure.ai.dataset/internal/cmd/output.go:29
- No leaf command registers
azdext.RegisterFlagOptions, so unsupported values such as-o yamlare accepted and silently fall through to human output. Register the supportedjson/tablevalues (and the intended default) on every runnable command so help, metadata, completion, and parse-time validation agree with the renderer.
v, err := cmd.Flags().GetString("output")
if err != nil {
return ""
}
return strings.ToLower(v)
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/models.go:44
- The service compatibility fields leak into
-o json, because commands encodeDatasetdirectly. The same logical URI is therefore emitted asdataUriordata_uridepending on which service spelling arrived, giving scripts an unstable schema. Keep both fields for decoding, but render a normalized output DTO with one canonical field name.
cli/azd/extensions/azure.ai.dataset/README.md:6 - This first-run command cannot work in this PR: the same README states that the registry entry is deliberately absent, so
azd extension install azure.ai.datasetcannot resolve. Replace this with complete local pack/publish instructions until the registry entry lands, or defer the install snippet with that release change.
$ azd extension install azure.ai.dataset
cli/azd/extensions/azure.ai.dataset/README.md:22
- The documented delete invocation omits the required
--versionflag, so copying it always fails with “--version is required.” Include the required argument in the command table.
| `azd ai dataset delete <name>` | Delete a dataset version |
cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:51
- Cleanup silently returns when listing/decoding fails and ignores every delete result, so failed live runs can leave datasets behind while still reporting successful cleanup. Make cleanup failures visible and require each delete to succeed.
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/resolver.go:139 - These environment-variable reads are not fully reflected in the repository's environment-variable reference:
FOUNDRY_PROJECT_ENDPOINTcurrently names onlyazure.ai.agents, andAZURE_AI_PROJECT_ENDPOINThas no entry. Updatecli/azd/docs/environment-variables.mdso both new extension inputs and their precedence are documented.
cli/azd/extensions/azure.ai.dataset/internal/cmd/root.go:21 - The PR description says every user-facing string lives in
internal/messages/messages.go, but this root help text and the command/flag help strings indataset.goare defined inline. Either centralize those strings as claimed or narrow the PR description; the current “69 messages in one file” review guarantee is not true.
Short: fmt.Sprintf(
"Register and version Foundry datasets from your terminal. %s",
color.YellowString("(Beta)"),
Both pass, so this is coverage rather than a fix, but neither was guarded and both are the kind that fail silently. A protocol-relative nextLink (//otherhost/path) inherits our scheme, so adding ResolveReference is precisely the change that could have opened a bypass; the test asserts the other host receives zero requests, since the pipeline attaches the caller's token. And a two-hop A-to-B-to-A cycle defeats any guard that only remembers the previous link, which is what the sibling eval paginator turned out to have.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 59 out of 60 changed files in this pull request and generated no new comments.
Suppressed comments (10)
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:232
- The API client explicitly treats a 404 from this endpoint as an unknown/versionless dataset, but this command returns that 404 before reaching the advertised empty-list behavior. Convert
IsNotFound(err)to an emptyDatasetList; otherwiseversions list <unknown>still exits nonzero.
list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
if err != nil {
return messages.ListingDatasetVersions(name, err)
}
cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45
- These linker targets still use the evaluation module path. This module is
azureaidataset, so the release build does not stamp itsVersion,Commit, orBuildDatevariables and the dataset User-Agent remains.../dev.
"-X 'azureaieval/internal/version.Version=$Version' " +
"-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
"-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "
cli/azd/extensions/azure.ai.dataset/build.ps1:44
- This copied evaluation module path means local PowerShell builds do not stamp
azureaidataset/internal/version; the binary keeps the development version in its User-Agent. PointVERSION_PATHat this module.
$VERSION_PATH = "azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/build.sh:36
- This copied evaluation module path means shell builds do not stamp
azureaidataset/internal/version; the binary keeps the development version in its User-Agent. PointVERSION_PATHat this module.
VERSION_PATH="azureaieval/internal/version"
cli/azd/extensions/azure.ai.dataset/.gitignore:5
- Debug logging creates
azd-ai-dataset-YYYY-MM-DD.log, but this pattern only ignores the old evaluation filename. Dataset debug logs will remain unignored and can be committed accidentally.
azd-ai-eval-*.log
cli/azd/extensions/azure.ai.dataset/ci-test.ps1:10
- The live harness checks
AZURE_AI_DATASET_E2E_LIVE, so this setup instruction names an environment variable that cannot enable the suite.
# AZURE_AI_EVAL_E2E_LIVE and a project endpoint. They are still type-checked
cli/azd/extensions/azure.ai.dataset/README.md:28
- Both references are stale: the evaluation extension exposes
azd ai eval generate, notazd ai eval dataset generate, and its configuration isazure.eval.yaml. As written, the documented command fails and points readers at the obsolete filename.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that
cli/azd/extensions/azure.ai.dataset/README.md:41
- This omits two implemented resolution levels and the
AZURE_AI_PROJECT_ENDPOINTfallback. Users relying on shared project context or the legacy key cannot infer why a value wins; document the same four-level order implemented byprojectctx.Resolve.
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:93
- Listing failures are currently indistinguishable from an absent dataset. A 403 or transport error makes
existsfalse, and both point probes discard their errors too, soupdatereports “does not exist” whilecreatemay proceed despite being unable to verify existence. Propagate non-404 errors and only treat empty/404 responses as absence.
This issue also appears on line 229 of the same file.
exists := err == nil && existing != nil && len(existing.Value) > 0
cli/azd/extensions/azure.ai.dataset/CHANGELOG.md:17
- No command in this extension calls
DownloadDatasetContent, so the first-release changelog promises a read-back capability that users cannot invoke. Remove this feature bullet or add the corresponding public command before release.
- Reads dataset content back, whether the service hands out a blob URI or the
container holding it.
Adds
azd ai datasetΓÇö a standalone extension for registering and versioning Foundry datasets.Why its own extension
A dataset is a general Foundry asset. Fine-tuning and other scenarios need one as much as evaluation does, so the CRUD group does not belong inside the evaluation extension. This extension has its own Go module, lint workflow and release pipeline, and no dependency on
azure.ai.evaluations.The line drawn with the evaluation extension: CRUD moved, generation stayed.
generatewrites thedatasets:entry into the evaluation configuration, so moving it would strand a config write across an extension boundary ΓÇö a generated dataset registered with the service and absent from configuration, whichazd upwould not reconcile.create/update/list/show/delete/versionsneed no configuration at all, which is why they could move.Surface
Notable
internal/messages/messages.go) so the whole voice of the CLI can be reviewed in one sitting. 69 messages.TYPE, notFORMAT. The API acceptsformaton upload and never returns it, so the previous column was blank for every dataset ever listed.azd-ext-azure-ai-evaluations) and debug log name (azd-ai-eval-*.log), so a dataset request could not be told from an eval one in a service log or a local one.Verified
go build,go vet -tags live,hero,go test ./...andgofmtclean. Exercised live against a Foundry project: create → 1.0, update → 2.0,versions list,show,list, and published to a local registry for a bug bash through realazd.Not included, deliberately
No
registry.jsonentry and nomicrosoft.foundrybundle dependency. Both need published artifacts; listing an unpublished extension in the bundle breaksazd extension install microsoft.foundryfor everyone. Those land after the first release.Open question for reviewers
azd ai dataset versions list <unknown-name>errors and exits 1 here, while the same command in the evaluation extension (#9500) lists nothing and exits 0. I tried making the eval side match this one and it broke two tests whose names are the argument against it ΓÇö "an unknown name lists nothing rather than failing" and "deleting an unregistered dataset is idempotent, not an error", the second because delete idempotence is verified by listing what is left.A list is a filter rather than a lookup, so empty-and-successful is defensible and
-o jsoncallers range over the array instead of branching on an error. By that reasoning this extension'sversions listis the one that should move.dataset showon an unknown name is not in question: that one is a lookup and should fail, as it does.Draft ΓÇö not requesting reviewers yet.
Fixes #9550