Skip to content

air: include MLflow run and experiment IDs in air get JSON - #7636

Draft
dimk0 wants to merge 4 commits into
mainfrom
cnxt-2640-air-get-mlflow-run-id
Draft

dimk0 wants to merge 4 commits into
mainfrom
cnxt-2640-air-get-mlflow-run-id

Conversation

@dimk0

@dimk0 dimk0 commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

Changes

  • air get -o json gains mlflow_experiment_id and mlflow_run_id, next to mlflow_url and filled from the same identifiers, so they are null exactly when mlflow_url is. The names match AiRuntimeTaskOutput.mlflow_experiment_id / mlflow_run_id.
  • A field comment and the changelog entry say where the IDs come from: for a finished run whose last retry never started an MLflow run, the newest earlier retry that has one. The IDs are null whenever no MLflow run is found, including when a get-output call fails.
  • Text output, air list, and URL formats are unchanged.

Why

Handing an AIR run to the next step needs its MLflow run ID. For example, the AI Runtime cookbook's serving recipe registers a model from the training run with source_training_run_id=<MLflow run ID>. Today the JSON exposes the ID only inside mlflow_url, and the text view's MLflow Run row shows the run name, so scripts and agents parse URLs. With this change, databricks air get <JOB_RUN_ID> -o json | jq -er .data.mlflow_run_id prints the ID, and exits non-zero when it is null. Internal ticket: CNXT-2640.

Tests

  • TestGetRunJSONMLflowIDs runs on the package's getRunServer helper and decodes the JSON into a map, so a null field and a missing key are told apart, and it asserts the exact mlflow_url. Cases: a finished run, a running run, IDs from an earlier retry, no MLflow run, an output with only an experiment ID, and a failing get-output call.
  • Mutation-checked: the test fails if the fields are not emitted, are emitted with omitempty, skip the earlier-retry path, ignore a running run, or accept an output with only one of the two IDs.
  • Updated the JSON expectations in TestBuildGetDataTerminationReasonJSON and TestBuildGetDataWaitingForComputeStatus, and the air/get, air/get-ai-runtime and air/get-pending acceptance goldens.
  • go test ./cmd/air/... and go test ./acceptance -run 'TestAccept/air/' pass; ./task lint-q, ./task fmt-q and ./task check-changelog are clean. Tests ran with DATABRICKS_WORKSPACE_ID unset: TestLogsCommandRepeatsRunLinkAfterStreaming also fails on main when that variable is set.

This pull request and its description were written by Isaac.

dimk0 and others added 2 commits November 14, 2026 23:42
`air get -o json` exposed the run's MLflow run only through mlflow_url, so
handing an AIR run to an MLflow-based next step (e.g. a registration job that
takes the MLflow run ID) meant parsing the URL. Add mlflow_experiment_id and
mlflow_run_id next to it; they are null exactly when mlflow_url is.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot added the AIR Databricks AI Runtime CLI label Nov 14, 2026
@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 5a5e05d4f | Workflow run

Regression test report

Selected 3 changed tests (limit=20, 0 not selected)

3 tests: regression=3 unreleased=0 coverage=0 failing=0
test branch main (20fef7c) latest (v1.26.0)
TestAccept/air/get ✅ ❌ ❌
TestAccept/air/get-ai-runtime ✅ ❌ ❌
TestAccept/air/get-pending ✅ ❌ ❌
TestAccept/air/get: branch ✅ | main (20fef7c) ❌ | latest (v1.26.0) ❌

main (20fef7c):

acceptance_test.go:1374: Diff:
        --- air/get/output.txt
        +++ /tmp/TestAcceptairget726462681/002/output.txt
        @@ -50,8 +50,6 @@
             "experiment_name": "my-exp",
             "dashboard_url": "[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]",
             "mlflow_url": "[DATABRICKS_URL]/ml/experiments/exp1/runs/run1/logs",
        -    "mlflow_experiment_id": "exp1",
        -    "mlflow_run_id": "run1",
             "est_remaining_seconds": null,
             "est_percent_complete": null
           }

latest (v1.26.0):

acceptance_test.go:1374: Diff:
        --- air/get/output.txt
        +++ /tmp/TestAcceptairget3130503287/002/output.txt
        @@ -49,9 +49,7 @@
             "attempt_number": 0,
             "experiment_name": "my-exp",
             "dashboard_url": "[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]",
        -    "mlflow_url": "[DATABRICKS_URL]/ml/experiments/exp1/runs/run1/logs",
        -    "mlflow_experiment_id": "exp1",
        -    "mlflow_run_id": "run1",
        +    "mlflow_url": "[DATABRICKS_URL]/ml/experiments/exp1/runs/run1/artifacts/logs/node_0",
             "est_remaining_seconds": null,
             "est_percent_complete": null
           }
TestAccept/air/get-ai-runtime: branch ✅ | main (20fef7c) ❌ | latest (v1.26.0) ❌

main (20fef7c):

acceptance_test.go:1374: Diff:
        --- air/get-ai-runtime/output.txt
        +++ /tmp/TestAcceptairget-ai-runtime2369766703/002/output.txt
        @@ -50,8 +50,6 @@
             "experiment_name": "my-exp",
             "dashboard_url": "[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]",
             "mlflow_url": "[DATABRICKS_URL]/ml/experiments/exp1/runs/run1/logs",
        -    "mlflow_experiment_id": "exp1",
        -    "mlflow_run_id": "run1",
             "est_remaining_seconds": null,
             "est_percent_complete": null
           }
        
    acceptance_test.go:1152: LOG.import: 
    acceptance_test.go:1152: LOG.mkdirs: 
FAIL

latest (v1.26.0):

acceptance_test.go:1374: Diff:
        --- air/get-ai-runtime/output.txt
        +++ /tmp/TestAcceptairget-ai-runtime3813051963/002/output.txt
        @@ -49,9 +49,7 @@
             "attempt_number": 0,
             "experiment_name": "my-exp",
             "dashboard_url": "[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]",
        -    "mlflow_url": "[DATABRICKS_URL]/ml/experiments/exp1/runs/run1/logs",
        -    "mlflow_experiment_id": "exp1",
        -    "mlflow_run_id": "run1",
        +    "mlflow_url": "[DATABRICKS_URL]/ml/experiments/exp1/runs/run1/artifacts/logs/node_0",
             "est_remaining_seconds": null,
             "est_percent_complete": null
           }
        
    acceptance_test.go:1152: LOG.import: 
    acceptance_test.go:1152: LOG.mkdirs: 
FAIL
TestAccept/air/get-pending: branch ✅ | main (20fef7c) ❌ | latest (v1.26.0) ❌

main (20fef7c):

acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n>>> [CLI] air get 123 -o json\n{\n  \"v\": 1,\n  \"ts\": \"[TIMESTAMP]\",\n  \"data\": {\n    \"run_id\": \"123\",\n    \"status\": \"PENDING\",\n    \"started_at\": null,\n    \"duration_seconds\": null,\n    \"attempt_number\": 0,\n    \"experiment_name\": \"my-exp\",\n    \"dashboard_url\": \"[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]\",\n    \"mlflow_url\": null,\n    \"mlflow_experiment_id\": null,\n    \"mlflow_run_id\": null,\n    \"status_message\": \"Waiting for accelerator compute capacity to become available...\",\n    \"est_remaining_seconds\": null,\n    \"est_percent_complete\": null\n  }\n}\n"
        	            	actual  : "\n>>> [CLI] air get 123 -o json\n{\n  \"v\": 1,\n  \"ts\": \"[TIMESTAMP]\",\n  \"data\": {\n    \"run_id\": \"123\",\n    \"status\": \"PENDING\",\n    \"started_at\": null,\n    \"duration_seconds\": null,\n    \"attempt_number\": 0,\n    \"experiment_name\": \"my-exp\",\n    \"dashboard_url\": \"[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]\",\n    \"mlflow_url\": null,\n    \"status_message\": \"Waiting for accelerator compute capacity to become available...\",\n    \"est_remaining_seconds\": null,\n    \"est_percent_complete\": null\n  }\n}\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -14,4 +14,2 @@
        	            	     "mlflow_url": null,
        	            	-    "mlflow_experiment_id": null,
        	            	-    "mlflow_run_id": null,
        	            	     "status_message": "Waiting for accelerator compute capacity to become available...",
        	Test:       	TestAccept/air/get-pending
        	Messages:   	air/get-pending/output.txt vs /tmp/TestAcceptairget-pending2090743915/002/output.txt

latest (v1.26.0):

acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n>>> [CLI] air get 123 -o json\n{\n  \"v\": 1,\n  \"ts\": \"[TIMESTAMP]\",\n  \"data\": {\n    \"run_id\": \"123\",\n    \"status\": \"PENDING\",\n    \"started_at\": null,\n    \"duration_seconds\": null,\n    \"attempt_number\": 0,\n    \"experiment_name\": \"my-exp\",\n    \"dashboard_url\": \"[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]\",\n    \"mlflow_url\": null,\n    \"mlflow_experiment_id\": null,\n    \"mlflow_run_id\": null,\n    \"status_message\": \"Waiting for accelerator compute capacity to become available...\",\n    \"est_remaining_seconds\": null,\n    \"est_percent_complete\": null\n  }\n}\n"
        	            	actual  : "\n>>> [CLI] air get 123 -o json\n{\n  \"v\": 1,\n  \"ts\": \"[TIMESTAMP]\",\n  \"data\": {\n    \"run_id\": \"123\",\n    \"status\": \"RUNNING\",\n    \"started_at\": null,\n    \"duration_seconds\": null,\n    \"attempt_number\": 0,\n    \"experiment_name\": \"my-exp\",\n    \"dashboard_url\": \"[DATABRICKS_URL]/jobs/runs/123?o=[WORKSPACE_ID]\",\n    \"mlflow_url\": null,\n    \"est_remaining_seconds\": null,\n    \"est_percent_complete\": null\n  }\n}\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -7,3 +7,3 @@
        	            	     "run_id": "123",
        	            	-    "status": "PENDING",
        	            	+    "status": "RUNNING",
        	            	     "started_at": null,
        	            	@@ -14,5 +14,2 @@
        	            	     "mlflow_url": null,
        	            	-    "mlflow_experiment_id": null,
        	            	-    "mlflow_run_id": null,
        	            	-    "status_message": "Waiting for accelerator compute capacity to become available...",
        	            	     "est_remaining_seconds": null,
        	Test:       	TestAccept/air/get-pending
        	Messages:   	air/get-pending/output.txt vs /tmp/TestAcceptairget-pending554855458/002/output.txt

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Nov 15, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: 5a5e05d

Run: 43771152837

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 271 18 3:38
✅​ aws-windows-latest-4core-16gb 273 16 3:04
✅​ azure linux-2core-8gb 270 18 3:22
✅​ azure-windows-latest-4core-16gb 272 16 3:02
✅​ gcp linux-2core-8gb 271 18 3:30
✅​ gcp-windows-latest-4core-16gb 273 16 2:56
Top 3 slowest tests (at least 2 minutes):
duration env testname
3:00 aws-windows-latest-4core-16gb TestAccept
2:58 azure-windows-latest-4core-16gb TestAccept
2:54 gcp-windows-latest-4core-16gb TestAccept

dimk0 and others added 2 commits November 15, 2026 22:16
Document the earlier-retry fallback and both meanings of null on mlflow_experiment_id/mlflow_run_id, and add the earlier-retry clause to the changelog.
Rework TestGetRunJSONMLflowIDs on getRunServer: tell null from a missing key, assert mlflow_url, and cover an earlier retry, a get-output error and a running run.

Co-authored-by: Isaac <no-reply@databricks.com>
- Add a case whose output has an experiment ID but no run ID; all three MLflow fields must be null.
- Say the IDs are null whenever no MLflow run is found, not only in two listed cases.

Co-authored-by: Isaac <no-reply@databricks.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AIR Databricks AI Runtime CLI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants