Skip to content

aitools: refresh only the plugin's own marketplace, and do not fail the install on it - #7473

Open
anton-107 wants to merge 1 commit into
aitools/plugin-probe-timeoutfrom
aitools/databricks-marketplace-refresh
Open

anton-107 wants to merge 1 commit into
aitools/plugin-probe-timeoutfrom
aitools/databricks-marketplace-refresh

Conversation

@anton-107

@anton-107 anton-107 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #7472.

Stacked on: aitools: raise the plugin probe timeout from 5s to 20s

Summary

Claude Code's plugin marketplace update with no name refreshes every marketplace the user has added, so one unreachable third-party marketplace failed the Databricks plugin install. The refresh now names the marketplace the Databricks plugin is listed in (claude-plugins-official for Claude Code), and a failed refresh no longer stops the install or the update: both go on from the local copy of the marketplace and, once that succeeds, warn and record the plugin version as unknown. Codex's plugin marketplace upgrade in aitools update names the marketplace too.

 InstallPluginForAgent (libs/aitools/installer/plugin.go)
   marketplace add (if absent)
-  claude plugin marketplace update
-    on error: remove the marketplace we added, return install-failed
+  claude plugin marketplace update <marketplace>
+    on error: keep the error, continue
   claude plugin install databricks@<marketplace>
     on error: remove the marketplace we added
-    return install-failed with the install error
+    return install-failed with the install error
+      + "(refreshing the marketplace also failed: <refresh error>)" if the refresh failed
-  record Version = <ref>
+  if the refresh failed and the marketplace was not just added by us:
+    warn that the plugin came from the local copy (with the agent's stderr)
+    record Version = "" (version unknown)
+  else record Version = <ref>

 UpdatePluginForAgent (aitools update)
-  claude plugin marketplace update
+  claude plugin marketplace update <recorded marketplace, or the agent's current one>
-    on error: return install-failed
+    on error: keep the error, continue
   claude plugin update ...
-  codex plugin marketplace upgrade
+  codex plugin marketplace upgrade <recorded marketplace, or the agent's current one>
+    on error: same as above
   codex plugin add ...
   on update error: return install-failed
+    + "(refreshing the marketplace also failed: <refresh error>)" if the refresh failed
-  record Version = <ref>
+  if the refresh failed: warn as above, and record Version = "" (version unknown)
+    unless the record already holds <ref>
+  else record Version = <ref>

Changelog fragment: .nextchanges/cli/aitools-marketplace-refresh.md.

Evidence

libs/aitools/installer/plugin_test.go, run at this head with plugin.go temporarily reverted to the parent's version and then restored:

  • Before:
    TestInstallPluginContinuesWhenRefreshFails                       FAIL
      claude-code: install-failed: /usr/bin/claude plugin marketplace update: boom
    TestInstallPluginRollsBackMarketplaceWhenRefreshAndInstallFail   FAIL
      detail "...marketplace update: refresh boom" does not contain "install boom (refreshing the marketplace also failed: "
    
    After:
    TestInstallPluginContinuesWhenRefreshFails                       PASS  (install runs, marketplace kept)
    TestInstallPluginRollsBackMarketplaceWhenRefreshAndInstallFail   PASS  (both errors in the detail, marketplace removed)
    ok  github.com/databricks/cli/libs/aitools/installer
    

The existing tests now also check that the refresh command names the marketplace (claude plugin marketplace update databricks-agent-skills, codex plugin marketplace upgrade databricks-agent-skills) and that a failed refresh leaves the recorded version empty unless we just added the marketplace. TestInstallPluginContinuesWhenRefreshFails is a table: a just-added marketplace (version recorded, no warning) and an already registered one (install goes on, no marketplace remove, InstalledMarketplace == false, empty version, warning with the agent's stderr). TestUpdatePluginForAgentClaudeOneStep also covers a record without a marketplace (falls back to the agent's marketplace). The failed install and failed update tests assert that the local-copy warning is not printed. For aitools update: TestUpdatePluginForAgentContinuesWhenRefreshFails (Claude Code and Codex: the refresh fails, the update still runs), TestUpdatePluginForAgentReportsRefreshWhenUpdateFails (both errors in the detail), and TestUpdateInstalledPluginsRecordsUnknownVersionWhenRefreshFails (the update succeeds, the state records an empty version). TestUpdateInstalledPluginsRecordsUnknownVersionWhenRefreshFails also covers a record that already holds the current release (kept). TestInstalledPluginAgents covers plugin records without a version, and TestUpdateCheckPrintsPluginState the three --check statuses. TestAgentStatusLabelKeepsUnknownStaleScope covers the aitools list label when one scope has an unknown version and the other is current. At this head: go build ./..., unit tests for libs/aitools/... and cmd/aitools/..., task test-exp-aitools, the cmd/aitools acceptance tests, lint and format on changed files, task checks, and go vet for windows and darwin on the aitools packages pass. Not run on Windows or macOS, and not run against a real claude binary.

Merge Danger

Door: two-way

Behavior change: a failing refresh used to fail aitools install with an install-failed error and remove the marketplace we had just added. Now the install goes on using the local copy of the marketplace; if that copy is stale and lacks the plugin, the install fails and the error includes both messages. A refresh failure that is followed by a successful install prints a warning with the agent's stderr and a hint to run databricks aitools update, and the plugin is recorded as "version unknown" so aitools update --check and aitools list don't report it as up to date. The warning is printed only after the install succeeds, and not when we just added the marketplace (that copy is fresh, so the version is recorded as usual). aitools list now shows "version unknown · update available" when one scope has an unknown version and another is current, instead of the current scope's version. No exit code changes. A refresh that hangs until the 60s command timeout used to end the run; now the install or update runs next and can wait up to another 60s, so on a machine where GitHub hangs instead of failing fast, aitools install or aitools update can take about a minute longer than before. aitools update used to skip the plugin with a warning when the refresh failed; now, for both Claude Code and Codex, it warns the same way, still runs the plugin update, and records the version as unknown, unless the record already holds the current release. aitools update --check shows an unknown version as "version unknown (update may be available)", and databricks version --check still counts a plugin recorded without a version as installed, so it does not suggest installing Databricks tooling. If the update itself fails, the plugin is skipped as before and the error includes both messages. Both commands refresh only the one marketplace now.

Blast Radius: small

Only the Claude Code plugin install and update path in databricks aitools. This relies on claude plugin marketplace update <name> and codex plugin marketplace upgrade <name> accepting a marketplace name; that is checked only through the stubbed commands in the tests here.

This pull request and its description were written by Isaac.

@anton-107
anton-107 requested review from a team and lennartkats-db as code owners November 11, 2026 14:25
@anton-107 anton-107 added the ai-auto PRs made by AI loops & factories, label Nov 11, 2026
@anton-107
anton-107 requested a review from misha-db November 11, 2026 14:25
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from cc3a109 to 3e0bf18 Compare November 11, 2026 14:33
@anton-107
anton-107 requested a review from a team as a code owner November 11, 2026 14:33
@anton-107
anton-107 requested a review from rauchy November 11, 2026 14:33
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 37c16a6 to feb1410 Compare November 11, 2026 14:33
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 06fd311b1 | Workflow run

Regression test report

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

12 tests: regression=8 unreleased=0 coverage=4 failing=0
test branch main (8b4f052) latest (v1.26.0)
TestAccept/cmd/aitools/install-host-metadata ✅ ❌ ❌
TestAccept/cmd/aitools/list/explicit-ref ✅ ✅ ✅
TestAccept/cmd/aitools/list/latest ✅ ✅ ✅
TestAccept/cmd/aitools/list/latest-lookup-fails ✅ ✅ ✅
TestAccept/cmd/aitools/list/pinned ✅ ✅ ✅
TestAccept/cmd/aitools/telemetry/apps-init-tip ✅ ❌ ❌
TestAccept/cmd/aitools/telemetry/hint ✅ ❌ ❌
TestAccept/cmd/aitools/telemetry/install ✅ ❌ ❌
TestAccept/cmd/aitools/telemetry/install-failure ✅ ❌ ❌
TestAccept/cmd/aitools/telemetry/update-uninstall ✅ ❌ ❌
TestAccept/cmd/aitools/telemetry/workspace-unreachable ✅ ❌ ❌
TestAccept/cmd/version ✅ ❌ ❌
TestAccept/cmd/aitools/install-host-metadata: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:46709" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== workspace unreachable: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code\nInstalling Databricks skills for Claude Code...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== --debug still shows the hostmetadata line, at debug level\nDebug: [hostmetadata] failed to fetch host metadata for https://unreachable.test\n"
        	            	actual  : "\n=== workspace unreachable: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code\nWarn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s\nInstalling Databricks skills for Claude Code...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== --debug still shows the hostmetadata line, at debug level\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -4,2 +4,3 @@
        	            	 >>> [CLI] aitools install --skills-only --scope=global --agents=claude-code
        	            	+Warn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s
        	            	 Installing Databricks skills for Claude Code...
        	            	@@ -10,3 +11,4 @@
        	            	 === --debug still shows the hostmetadata line, at debug level
        	            	-Debug: [hostmetadata] failed to fetch host metadata for https://unreachable.test
        	            	 
        	            	+Exit code: 1
        	            	+
        	Test:       	TestAccept/cmd/aitools/install-host-metadata
        	Messages:   	cmd/aitools/install-host-metadata/output.txt vs /tmp/TestAcceptcmdaitoolsinstall-host-metadata4030549039/002/output.txt

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:33445" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== workspace unreachable: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code\nInstalling Databricks skills for Claude Code...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== --debug still shows the hostmetadata line, at debug level\nDebug: [hostmetadata] failed to fetch host metadata for https://unreachable.test\n"
        	            	actual  : "\n=== workspace unreachable: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code\nWarn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s\nInstalling Databricks skills for Claude Code...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== --debug still shows the hostmetadata line, at debug level\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -4,2 +4,3 @@
        	            	 >>> [CLI] aitools install --skills-only --scope=global --agents=claude-code
        	            	+Warn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s
        	            	 Installing Databricks skills for Claude Code...
        	            	@@ -10,3 +11,4 @@
        	            	 === --debug still shows the hostmetadata line, at debug level
        	            	-Debug: [hostmetadata] failed to fetch host metadata for https://unreachable.test
        	            	 
        	            	+Exit code: 1
        	            	+
        	Test:       	TestAccept/cmd/aitools/install-host-metadata
        	Messages:   	cmd/aitools/install-host-metadata/output.txt vs /tmp/TestAcceptcmdaitoolsinstall-host-metadata2525003367/002/output.txt
TestAccept/cmd/aitools/telemetry/apps-init-tip: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43523" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43523" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== apps init with flags prints the tip: one hint-shown event per hint printed\n\n>>> [CLI] apps init --template ./template --skip-install --name app-tip\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-tip\n  Files: 3\n\nTip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.\n\n>>> print_telemetry\n{\"command\":\"apps_init\",\"exit_code\":0,\"aitools_hint_event\":{\"hint_kind\":\"INSTALL_TEXT\"}}\n{\"command\":\"apps_init\",\"exit_code\":0,\"aitools_hint_event\":{\"hint_kind\":\"APPS_INIT_TIP\"}}\n\n=== plugin installed: no hint, no tip, no event\n\n>>> [CLI] apps init --template ./template --skip-install --name app-plugin\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-plugin\n  Files: 3\n\n\n>>> print_telemetry\n"
        	            	actual  : "\n=== apps init with flags prints the tip: one hint-shown event per hint printed\n\n>>> [CLI] apps init --template ./template --skip-install --name app-tip\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-tip\n  Files: 3\n\nTip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.\n\n>>> print_telemetry\n\n=== plugin installed: no hint, no tip, no event\n\n>>> [CLI] apps init --template ./template --skip-install --name app-plugin\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-plugin\n  Files: 3\n\nTip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.\n\n>>> print_telemetry\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -15,4 +15,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"apps_init","exit_code":0,"aitools_hint_event":{"hint_kind":"INSTALL_TEXT"}}
        	            	-{"command":"apps_init","exit_code":0,"aitools_hint_event":{"hint_kind":"APPS_INIT_TIP"}}
        	            	 
        	            	@@ -28,2 +26,3 @@
        	            	 
        	            	+Tip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.
        	            	 
        	Test:       	TestAccept/cmd/aitools/telemetry/apps-init-tip
        	Messages:   	cmd/aitools/telemetry/apps-init-tip/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryapps-init-tip778348868/002/output.txt
    acceptance_test.go:1123: LOG.apps-init: 
    acceptance_test.go:1123: LOG.apps-init-plugin:

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43431" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43431" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== apps init with flags prints the tip: one hint-shown event per hint printed\n\n>>> [CLI] apps init --template ./template --skip-install --name app-tip\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-tip\n  Files: 3\n\nTip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.\n\n>>> print_telemetry\n{\"command\":\"apps_init\",\"exit_code\":0,\"aitools_hint_event\":{\"hint_kind\":\"INSTALL_TEXT\"}}\n{\"command\":\"apps_init\",\"exit_code\":0,\"aitools_hint_event\":{\"hint_kind\":\"APPS_INIT_TIP\"}}\n\n=== plugin installed: no hint, no tip, no event\n\n>>> [CLI] apps init --template ./template --skip-install --name app-plugin\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-plugin\n  Files: 3\n\n\n>>> print_telemetry\n"
        	            	actual  : "\n=== apps init with flags prints the tip: one hint-shown event per hint printed\n\n>>> [CLI] apps init --template ./template --skip-install --name app-tip\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-tip\n  Files: 3\n\nTip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.\n\n>>> print_telemetry\n\n=== plugin installed: no hint, no tip, no event\n\n>>> [CLI] apps init --template ./template --skip-install --name app-plugin\n✔ Creating project\n\n✔ Project created successfully!\n\n  Location: [TEST_TMP_DIR]/app-plugin\n  Files: 3\n\nTip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.\n\n>>> print_telemetry\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -15,4 +15,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"apps_init","exit_code":0,"aitools_hint_event":{"hint_kind":"INSTALL_TEXT"}}
        	            	-{"command":"apps_init","exit_code":0,"aitools_hint_event":{"hint_kind":"APPS_INIT_TIP"}}
        	            	 
        	            	@@ -28,2 +26,3 @@
        	            	 
        	            	+Tip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.
        	            	 
        	Test:       	TestAccept/cmd/aitools/telemetry/apps-init-tip
        	Messages:   	cmd/aitools/telemetry/apps-init-tip/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryapps-init-tip4089770871/002/output.txt
    acceptance_test.go:1123: LOG.apps-init: 
    acceptance_test.go:1123: LOG.apps-init-plugin:
TestAccept/cmd/aitools/telemetry/hint: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43667" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43667" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== hint printed on an authenticated command: one hint-shown event is uploaded\n\n>>> [CLI] jobs list\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n\n>>> print_telemetry\n{\"command\":\"jobs_list\",\"exit_code\":0,\"aitools_hint_event\":{\"hint_kind\":\"INSTALL_TEXT\"}}\n\n=== hint printed on a command that resolves no auth: nothing is uploaded\n\n>>> [CLI] version\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\nDatabricks CLI v[CLI_VERSION]\n\n>>> print_telemetry\n\n=== plugin installed: no hint, no event\n\n>>> [CLI] jobs list\n\n>>> print_telemetry\n"
        	            	actual  : "\n=== hint printed on an authenticated command: one hint-shown event is uploaded\n\n>>> [CLI] jobs list\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n\n>>> print_telemetry\n\n=== hint printed on a command that resolves no auth: nothing is uploaded\n\n>>> [CLI] version\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\nDatabricks CLI v[CLI_VERSION]\n\n>>> print_telemetry\n\n=== plugin installed: no hint, no event\n\n>>> [CLI] jobs list\n\n>>> print_telemetry\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -7,3 +7,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"jobs_list","exit_code":0,"aitools_hint_event":{"hint_kind":"INSTALL_TEXT"}}
        	            	 
        	Test:       	TestAccept/cmd/aitools/telemetry/hint
        	Messages:   	cmd/aitools/telemetry/hint/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryhint678215633/002/output.txt

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40337" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40337" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== hint printed on an authenticated command: one hint-shown event is uploaded\n\n>>> [CLI] jobs list\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n\n>>> print_telemetry\n{\"command\":\"jobs_list\",\"exit_code\":0,\"aitools_hint_event\":{\"hint_kind\":\"INSTALL_TEXT\"}}\n\n=== hint printed on a command that resolves no auth: nothing is uploaded\n\n>>> [CLI] version\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\nDatabricks CLI v[CLI_VERSION]\n\n>>> print_telemetry\n\n=== plugin installed: no hint, no event\n\n>>> [CLI] jobs list\n\n>>> print_telemetry\n"
        	            	actual  : "\n=== hint printed on an authenticated command: one hint-shown event is uploaded\n\n>>> [CLI] jobs list\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\n\n>>> print_telemetry\n\n=== hint printed on a command that resolves no auth: nothing is uploaded\n\n>>> [CLI] version\nDatabricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install\nDatabricks CLI v[CLI_VERSION]\n\n>>> print_telemetry\n\n=== plugin installed: no hint, no event\n\n>>> [CLI] jobs list\n\n>>> print_telemetry\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -7,3 +7,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"jobs_list","exit_code":0,"aitools_hint_event":{"hint_kind":"INSTALL_TEXT"}}
        	            	 
        	Test:       	TestAccept/cmd/aitools/telemetry/hint
        	Messages:   	cmd/aitools/telemetry/hint/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryhint3399379300/002/output.txt
TestAccept/cmd/aitools/telemetry/install: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:42117" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:42117" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall2525328168/002/output.txt
        @@ -8,14 +8,7 @@
         Installed 1 skill.
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SKILLS_INSTALLED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":0,"aitools_install_event":{"agents":["CLAUDE_CODE"],"scope":"GLOBAL","error_category":"TYPE_UNSPECIFIED"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CLAUDE_CODE"],"scope":"GLOBAL"}}
         
         === databricks-cli auth with a valid cached token: the event is uploaded
         
        @@ -26,14 +18,7 @@
         Installed 1 skill.
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SKILLS_INSTALLED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":0,"aitools_install_event":{"agents":["CLAUDE_CODE"],"scope":"GLOBAL","error_category":"TYPE_UNSPECIFIED"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CLAUDE_CODE"],"scope":"GLOBAL"}}
         
         === databricks-cli auth with an expired cached token: no refresh, nothing is uploaded
         
        @@ -44,6 +28,22 @@
         Installed 1 skill.
         
         >>> print_requests.py //oidc/v1/token //telemetry-ext
        +{
        +  "method": "POST",
        +  "path": "/oidc/v1/token",
        +  "raw_body": "client_id=databricks-cli&grant_type=refresh_token&refresh_token=test-refresh-token"
        +}
        +{
        +  "method": "POST",
        +  "path": "/telemetry-ext",
        +  "body": {
        +    "uploadTime": [UNIX_TIME_MILLIS],
        +    "items": [],
        +    "protoLogs": [
        +      "{\"frontend_log_event_id\":\"[UUID][0]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID][1]\",\"version\":\"[CLI_VERSION]\",\"command\":\"aitools_install\",\"operating_system\":\"linux\",\"execution_time_ms\":6,\"exit_code\":0},\"aitools_install_event\":{\"agents\":[\"CLAUDE_CODE\"],\"scope\":\"GLOBAL\",\"error_category\":\"TYPE_UNSPECIFIED\"}}}}"
        +    ]
        +  }
        +}
         
         >>> gron.py [TEST_TMP_DIR]/home/.databricks/token-cache.json
        -json.tokens.test-profile.access_token = "expired-access-token";
        +json.tokens.test-profile.access_token = "oauth-token";
        
FAIL

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:42849" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:42849" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall2296488027/002/output.txt
        @@ -8,14 +8,7 @@
         Installed 1 skill.
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SKILLS_INSTALLED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":0,"aitools_install_event":{"agents":["CLAUDE_CODE"],"scope":"GLOBAL","error_category":"TYPE_UNSPECIFIED"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CLAUDE_CODE"],"scope":"GLOBAL"}}
         
         === databricks-cli auth with a valid cached token: the event is uploaded
         
        @@ -26,14 +18,7 @@
         Installed 1 skill.
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SKILLS_INSTALLED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":0,"aitools_install_event":{"agents":["CLAUDE_CODE"],"scope":"GLOBAL","error_category":"TYPE_UNSPECIFIED"}}
        -{"command":"aitools_install","exit_code":0,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CLAUDE_CODE"],"scope":"GLOBAL"}}
         
         === databricks-cli auth with an expired cached token: no refresh, nothing is uploaded
         
        @@ -44,6 +28,22 @@
         Installed 1 skill.
         
         >>> print_requests.py //oidc/v1/token //telemetry-ext
        +{
        +  "method": "POST",
        +  "path": "/oidc/v1/token",
        +  "raw_body": "client_id=databricks-cli&grant_type=refresh_token&refresh_token=test-refresh-token"
        +}
        +{
        +  "method": "POST",
        +  "path": "/telemetry-ext",
        +  "body": {
        +    "uploadTime": [UNIX_TIME_MILLIS],
        +    "items": [],
        +    "protoLogs": [
        +      "{\"frontend_log_event_id\":\"[UUID][0]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID][1]\",\"version\":\"[CLI_VERSION]\",\"command\":\"aitools_install\",\"operating_system\":\"linux\",\"execution_time_ms\":12,\"exit_code\":0},\"aitools_install_event\":{\"agents\":[\"CLAUDE_CODE\"],\"scope\":\"GLOBAL\",\"error_category\":\"TYPE_UNSPECIFIED\"}}}}"
        +    ]
        +  }
        +}
         
         >>> gron.py [TEST_TMP_DIR]/home/.databricks/token-cache.json
        -json.tokens.test-profile.access_token = "expired-access-token";
        +json.tokens.test-profile.access_token = "oauth-token";
TestAccept/cmd/aitools/telemetry/install-failure: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40865" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40865" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure2422809626/002/output.txt
        @@ -8,12 +8,6 @@
         Error: skill "nonexistent" not found
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"SKILL_NOT_FOUND","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"SKILL_NOT_FOUND"}}
         
         === a manifest the server does not have is MANIFEST_NOT_FOUND
        @@ -25,12 +19,6 @@
         Error: skills manifest at databricks-agent-skills@missing-ref: not found
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"MANIFEST_NOT_FOUND","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"MANIFEST_NOT_FOUND"}}
         
         === any other HTTP error status from the manifest fetch is not classified yet (the error is untyped)
        @@ -42,12 +30,6 @@
         Error: failed to fetch manifest: HTTP 500
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"UNCATEGORIZED","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"UNCATEGORIZED"}}
         
         === a named agent skipped for the scope takes its category from the agent's outcome
        @@ -57,10 +39,4 @@
         Error: Cursor: does not support project-scoped skills
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"PROJECT"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"PROJECT"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"PROJECT"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"UNSUPPORTED_SCOPE","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"PROJECT"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"PROJECT","error_category":"TYPE_UNSPECIFIED","agent_results":[{"agent":"CURSOR","error_category":"UNSUPPORTED_SCOPE"}]}}

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43897" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43897" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure442674684/002/output.txt
        @@ -8,12 +8,6 @@
         Error: skill "nonexistent" not found
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"SKILL_NOT_FOUND","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"SKILL_NOT_FOUND"}}
         
         === a manifest the server does not have is MANIFEST_NOT_FOUND
        @@ -25,12 +19,6 @@
         Error: skills manifest at databricks-agent-skills@missing-ref: not found
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"MANIFEST_NOT_FOUND","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"MANIFEST_NOT_FOUND"}}
         
         === any other HTTP error status from the manifest fetch is not classified yet (the error is untyped)
        @@ -42,12 +30,6 @@
         Error: failed to fetch manifest: HTTP 500
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"GLOBAL"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"UNCATEGORIZED","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"GLOBAL"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"UNCATEGORIZED"}}
         
         === a named agent skipped for the scope takes its category from the agent's outcome
        @@ -57,10 +39,4 @@
         Error: Cursor: does not support project-scoped skills
         
         >>> print_telemetry
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STARTED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"FLAGS_VALIDATED","interactive":false,"install_mode":"SKILLS_ONLY"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"SCOPE_RESOLVED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"PROJECT"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"AGENTS_SELECTED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"PROJECT"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"STEP_COMPLETED","step":"PLAN_CONFIRMED","interactive":false,"install_mode":"SKILLS_ONLY","scope":"PROJECT"}}
        -{"command":"aitools_install","exit_code":1,"aitools_command_event":{"command":"INSTALL","stage":"FINISHED","outcome":"FAILURE","error_category":"UNSUPPORTED_SCOPE","interactive":false,"install_mode":"SKILLS_ONLY","agents":["CURSOR"],"scope":"PROJECT"}}
         {"command":"aitools_install","exit_code":1,"aitools_install_event":{"agents":["CURSOR"],"scope":"PROJECT","error_category":"TYPE_UNSPECIFIED","agent_results":[{"agent":"CURSOR","error_category":"UNSUPPORTED_SCOPE"}]}}
TestAccept/cmd/aitools/telemetry/update-uninstall: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:46007" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:46007" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== update: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n>>> print_telemetry\n{\"command\":\"aitools_update\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UPDATE\",\"stage\":\"STARTED\",\"interactive\":false}}\n{\"command\":\"aitools_update\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UPDATE\",\"stage\":\"FINISHED\",\"outcome\":\"SUCCESS\",\"interactive\":false,\"scope\":\"GLOBAL\"}}\n\n=== uninstall: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n\n>>> print_telemetry\n{\"command\":\"aitools_uninstall\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UNINSTALL\",\"stage\":\"STARTED\",\"interactive\":false}}\n{\"command\":\"aitools_uninstall\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UNINSTALL\",\"stage\":\"FINISHED\",\"outcome\":\"SUCCESS\",\"interactive\":false,\"scope\":\"GLOBAL\"}}\n"
        	            	actual  : "\n=== update: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n>>> print_telemetry\n\n=== uninstall: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n\n>>> print_telemetry\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -7,4 +7,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"aitools_update","exit_code":0,"aitools_command_event":{"command":"UPDATE","stage":"STARTED","interactive":false}}
        	            	-{"command":"aitools_update","exit_code":0,"aitools_command_event":{"command":"UPDATE","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"scope":"GLOBAL"}}
        	            	 
        	            	@@ -16,4 +14,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"aitools_uninstall","exit_code":0,"aitools_command_event":{"command":"UNINSTALL","stage":"STARTED","interactive":false}}
        	            	-{"command":"aitools_uninstall","exit_code":0,"aitools_command_event":{"command":"UNINSTALL","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"scope":"GLOBAL"}}
        	            	 
        	Test:       	TestAccept/cmd/aitools/telemetry/update-uninstall
        	Messages:   	cmd/aitools/telemetry/update-uninstall/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryupdate-uninstall1626800478/002/output.txt
    acceptance_test.go:1123: 
        LOG.install: Installing Databricks skills for Cursor...
        LOG.install: Using skills version test-ref
        LOG.install: Fetching skills manifest...
        LOG.install: Installed 1 skill.
    acceptance_test.go:1123: LOG.install-telemetry: {"command":"aitools_install","exit_code":0,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"TYPE_UNSPECIFIED"}}

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:39657" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:39657" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== update: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n>>> print_telemetry\n{\"command\":\"aitools_update\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UPDATE\",\"stage\":\"STARTED\",\"interactive\":false}}\n{\"command\":\"aitools_update\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UPDATE\",\"stage\":\"FINISHED\",\"outcome\":\"SUCCESS\",\"interactive\":false,\"scope\":\"GLOBAL\"}}\n\n=== uninstall: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n\n>>> print_telemetry\n{\"command\":\"aitools_uninstall\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UNINSTALL\",\"stage\":\"STARTED\",\"interactive\":false}}\n{\"command\":\"aitools_uninstall\",\"exit_code\":0,\"aitools_command_event\":{\"command\":\"UNINSTALL\",\"stage\":\"FINISHED\",\"outcome\":\"SUCCESS\",\"interactive\":false,\"scope\":\"GLOBAL\"}}\n"
        	            	actual  : "\n=== update: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n>>> print_telemetry\n\n=== uninstall: STARTED, FINISHED with SUCCESS\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n\n>>> print_telemetry\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -7,4 +7,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"aitools_update","exit_code":0,"aitools_command_event":{"command":"UPDATE","stage":"STARTED","interactive":false}}
        	            	-{"command":"aitools_update","exit_code":0,"aitools_command_event":{"command":"UPDATE","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"scope":"GLOBAL"}}
        	            	 
        	            	@@ -16,4 +14,2 @@
        	            	 >>> print_telemetry
        	            	-{"command":"aitools_uninstall","exit_code":0,"aitools_command_event":{"command":"UNINSTALL","stage":"STARTED","interactive":false}}
        	            	-{"command":"aitools_uninstall","exit_code":0,"aitools_command_event":{"command":"UNINSTALL","stage":"FINISHED","outcome":"SUCCESS","interactive":false,"scope":"GLOBAL"}}
        	            	 
        	Test:       	TestAccept/cmd/aitools/telemetry/update-uninstall
        	Messages:   	cmd/aitools/telemetry/update-uninstall/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryupdate-uninstall2005365344/002/output.txt
    acceptance_test.go:1123: 
        LOG.install: Installing Databricks skills for Cursor...
        LOG.install: Using skills version test-ref
        LOG.install: Fetching skills manifest...
        LOG.install: Installed 1 skill.
    acceptance_test.go:1123: LOG.install-telemetry: {"command":"aitools_install","exit_code":0,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"TYPE_UNSPECIFIED"}}
TestAccept/cmd/aitools/telemetry/workspace-unreachable: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:38071" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:38071" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== install: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=cursor\nInstalling Databricks skills for Cursor...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== update: the background auth resolution prints nothing\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n=== uninstall: the background auth resolution prints nothing\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n"
        	            	actual  : "\n=== install: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=cursor\nWarn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s\nInstalling Databricks skills for Cursor...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== update: the background auth resolution prints nothing\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n=== uninstall: the background auth resolution prints nothing\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -4,2 +4,3 @@
        	            	 >>> [CLI] aitools install --skills-only --scope=global --agents=cursor
        	            	+Warn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s
        	            	 Installing Databricks skills for Cursor...
        	Test:       	TestAccept/cmd/aitools/telemetry/workspace-unreachable
        	Messages:   	cmd/aitools/telemetry/workspace-unreachable/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryworkspace-unreachable345020077/002/output.txt

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:44593" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:44593" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:642
        	Error:      	Not equal: 
        	            	expected: "\n=== install: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=cursor\nInstalling Databricks skills for Cursor...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== update: the background auth resolution prints nothing\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n=== uninstall: the background auth resolution prints nothing\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n"
        	            	actual  : "\n=== install: the background auth resolution prints nothing\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=cursor\nWarn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s\nInstalling Databricks skills for Cursor...\nUsing skills version test-ref\nFetching skills manifest...\nInstalled 1 skill.\n\n=== update: the background auth resolution prints nothing\n\n>>> [CLI] aitools update --scope=global\nAlready up to date.\n\n=== uninstall: the background auth resolution prints nothing\n\n>>> [CLI] aitools uninstall --scope=global\nUninstalled 1 skill.\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -4,2 +4,3 @@
        	            	 >>> [CLI] aitools install --skills-only --scope=global --agents=cursor
        	            	+Warn: [hostmetadata] failed to fetch host metadata for https://unreachable.test, will skip for 1m0s
        	            	 Installing Databricks skills for Cursor...
        	Test:       	TestAccept/cmd/aitools/telemetry/workspace-unreachable
        	Messages:   	cmd/aitools/telemetry/workspace-unreachable/output.txt vs /tmp/TestAcceptcmdaitoolstelemetryworkspace-unreachable787006104/002/output.txt
TestAccept/cmd/version: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1345: Diff:
        --- cmd/version/output.txt
        +++ /tmp/TestAcceptcmdversion3900054692/002/output.txt
        @@ -24,7 +24,6 @@
         >>> [CLI] version --check
         Databricks CLI v[CLI_VERSION]
         This is a development build; skipping the update check.
        -Tip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.
         
         === version --check --output json with a coding agent prints no tip

latest (v1.26.0):

acceptance_test.go:1345: Diff:
        --- cmd/version/output.txt
        +++ /tmp/TestAcceptcmdversion4247099555/002/output.txt
        @@ -8,7 +8,7 @@
         
         >>> [CLI] version --check
         Databricks CLI v[CLI_VERSION]
        -This is a development build; skipping the update check.
        +Could not reach GitHub to check for a newer version. See https://github.com/databricks/cli/releases for the latest release.
         
         === version --check --output json
         
        @@ -16,15 +16,14 @@
         {
           "current_version": "[CLI_VERSION]",
           "update_available": false,
        -  "development_build": true
        +  "check_failed": true
         }
         
         === version --check with a coding agent and no Databricks tooling
         
         >>> [CLI] version --check
         Databricks CLI v[CLI_VERSION]
        -This is a development build; skipping the update check.
        -Tip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.
        +Could not reach GitHub to check for a newer version. See https://github.com/databricks/cli/releases for the latest release.
         
         === version --check --output json with a coding agent prints no tip
         
        @@ -32,7 +25,7 @@
         {
           "current_version": "[CLI_VERSION]",
           "update_available": false,
        -  "development_build": true
        +  "check_failed": true
         }
         
         === version --check from Claude Code prints only the root hint
        @@ -40,4 +33,4 @@
         >>> [CLI] version --check
         Databricks skills are not installed. To work with Databricks reliably, first run: databricks aitools install
         Databricks CLI v[CLI_VERSION]
        -This is a development build; skipping the update check.
        +Could not reach GitHub to check for a newer version. See https://github.com/databricks/cli/releases for the latest release.

@eng-dev-ecosystem-bot

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

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: 06fd311

Run: 43200332263

Env 🔄​flaky 💚​RECOVERED ✅​pass 🙈​skip Time
💚​ aws linux-2core-8gb 1 270 27 13:10
💚​ aws-windows-latest-4core-16gb 1 272 25 8:23
💚​ azure linux-2core-8gb 1 269 27 9:29
🔄​ azure-windows-latest-4core-16gb 1 1 270 25 8:48
🔄​ gcp linux-2core-8gb 2 1 268 27 15:42
💚​ gcp-windows-latest-4core-16gb 1 272 25 12:50
Test Name aws linux-2core-8gb aws-windows-latest-4core-16gb azure linux-2core-8gb azure-windows-latest-4core-16gb gcp linux-2core-8gb gcp-windows-latest-4core-16gb
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🔄​ TestSyncIncrementalSyncFileToPythonNotebook ✅​p ✅​p ✅​p 🔄​f ✅​p ✅​p
🔄​ TestFilerWorkspaceNotebook ✅​p ✅​p ✅​p ✅​p 🔄​f ✅​p
🔄​ TestFilerWorkspaceNotebook/sqlNb.sql ✅​p ✅​p ✅​p ✅​p 🔄​f ✅​p
Top 15 slowest tests (at least 2 minutes):
duration env testname
4:20 gcp linux-2core-8gb TestFilerWorkspaceFilesExtensionsReadDir
4:08 gcp-windows-latest-4core-16gb TestAccept
4:01 azure-windows-latest-4core-16gb TestAccept
3:59 aws linux-2core-8gb TestAccept
3:50 aws-windows-latest-4core-16gb TestAccept
3:46 gcp linux-2core-8gb TestAccept
3:42 azure linux-2core-8gb TestAccept
2:36 gcp-windows-latest-4core-16gb TestFilerRecursiveDelete/dbfs
2:20 gcp-windows-latest-4core-16gb TestFsCpDirToDirWithOverwriteFlag/dbfs_to_dbfs
2:19 azure-windows-latest-4core-16gb TestFilerWorkspaceFilesExtensionsReadDir
2:16 gcp-windows-latest-4core-16gb TestFilerRecursiveDelete/workspace_files_extensions
2:12 gcp linux-2core-8gb TestFsCpDirToDirFileNotOverwritten/dbfs_to_dbfs
2:10 aws linux-2core-8gb TestFilerWorkspaceFilesExtensionsReadDir
2:08 gcp linux-2core-8gb TestFsCpDirToDirFileNotOverwritten/dbfs_to_uc-volumes
2:01 gcp linux-2core-8gb TestFilerReadWrite/dbfs

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Automated review from simonfaltum's review agent (pr-review skill) at feb1410. Each finding was verified against the code; simonfaltum may follow up.

Not approving yet: 2 open design questions.

This PR makes the Claude Code marketplace refresh name the plugin's own marketplace, and stops a failed refresh from failing aitools install. The named refresh is a clean fix, and all CI checks pass. Nothing is blocking, but the continue-on-failure path hides a likely stale install: it logs only at debug and records the latest version. The changelog also names the wrong marketplace, and Codex's update step still refreshes every marketplace. Two design questions remain: whether the non-fatal fallback is needed, and why update and install now treat a refresh failure differently.

Questions

  1. aitools install now goes on when claude plugin marketplace update <name> fails, but aitools update still stops on the same refresh failure (UpdatePluginForAgent returns on the first failing step, plugin.go:312-316). Is that difference intended? If it is, could the reason go in a code comment? If not, should update also try plugin update after a failed refresh, the same way install does?
  2. Naming the marketplace in the refresh already stops an unrelated, unreachable marketplace from failing the install. After that change, a refresh fails only when the plugin's own marketplace can't be refreshed, which is when the local copy is most likely out of date. AGENTS.md asks to avoid speculative fallbacks. Is the continue-on-refresh-failure path still needed? If so, would you surface it (warn, and don't record the latest version)? Or would you keep only the named refresh and restore the fatal refresh with rollback?

Comments outside the changed lines

  • [nice to have] libs/aitools/installer/plugin.go:296: When the refresh fails, the install now proceeds from the local marketplace copy, but the record still stores Version: DisplaySkillsVersion(ref). If claude-plugins-official is already registered with an old copy and the refresh fails, the install succeeds with an older plugin. The CLI prints databricks plugin v<latest>, and aitools update --check (cmd/aitools/update.go:280-285) and aitools list report "up to date". Before this PR a failed refresh stopped the install, so this drift could not happen. Consider leaving Version empty when refreshErr != nil, so the record shows "version unknown" until the next successful update.
  • [nit] libs/aitools/installer/plugin.go:108: The PR adds recordedMarketplace, but recordedInstallTarget still has the same rec.Marketplace → agent.Plugin.Marketplace fallback written inline. Write return plugin + "@" + recordedMarketplace(agent, rec) so the refreshed marketplace and the update target cannot drift.

Comment thread libs/aitools/installer/plugin.go Outdated
}
}
return PluginRecord{}, &BlockedError{Agent: agent.Name, Reason: ReasonInstallFailed, Detail: stderrOf(err)}
log.Debugf(ctx, "%s plugin marketplace refresh failed, installing from the local copy: %v", agent.DisplayName, err)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] When the refresh fails and the install succeeds, the user gets a clean success with no hint that the plugin may be out of date. .agents/rules/style-guide-go.md:237 says to "Warn for things the user should know about and might act on". The line also logs %v, which for a *process.ProcessError is only "<command>: exit status N" (libs/process/background.go:25-27), so even --debug loses the reason. Log at Warn with stderrOf(err) and an actionable hint, e.g. "could not refresh the marketplace, installed from the local copy; run databricks aitools update later: ".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 02922d2: the refresh failure is now logged with log.Warnf and stderrOf(err), with the hint: "Could not refresh the marketplace, installing the plugin from the local copy; run databricks aitools update later: ".

@@ -0,0 +1 @@
* `databricks aitools install` refreshes only the Databricks Claude Code marketplace, and installs from the local copy when the refresh fails, so another unreachable marketplace no longer blocks the install. ([#7473](https://github.com/databricks/cli/pull/7473))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] The entry says install "refreshes only the Databricks Claude Code marketplace", but the Claude Code registry entry uses claudePlugin(), i.e. Anthropic's shared claude-plugins-official marketplace (libs/aitools/agents/agents.go:163-168, 182). The refresh therefore runs claude plugin marketplace update claude-plugins-official. The tests' claudeAgent() helper uses databricks-agent-skills, which is probably where the wording came from. Reword it to something like "refreshes only the marketplace the Databricks plugin is listed in", and update the PR title the same way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 02922d2: the fragment now says install and update "refresh only the marketplace the Databricks plugin is listed in", and mentions the warning and the "version unknown" record. PR title updated to "refresh only the plugin's own marketplace". The commit subject stays as is for now.

Comment thread libs/aitools/installer/plugin.go Outdated
case agents.NameClaudeCode:
return [][]string{
marketplaceUpdateArgs(agent),
marketplaceUpdateArgs(agent, recordedMarketplace(agent, rec)),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] The Codex branch a few lines up (line 187) still runs a bare codex plugin marketplace upgrade. codex plugin marketplace upgrade --help says "Omit MARKETPLACE_NAME to upgrade all configured Git marketplaces", so one unreachable third-party marketplace still fails databricks aitools update for Codex, which is the failure this PR fixes for Claude. Pass the name there too: {"plugin", "marketplace", "upgrade", recordedMarketplace(agent, rec)}.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 02922d2: Codex's update step now runs codex plugin marketplace upgrade <recordedMarketplace(agent, rec)>; TestUpdatePluginForAgentCodexTwoStep asserts codex plugin marketplace upgrade databricks-agent-skills. Changelog and PR body mention it.

Comment thread libs/aitools/installer/plugin_test.go Outdated
Comment on lines +167 to +168
// A refresh failure (e.g. another marketplace the user added is unreachable)
// used to fail the install although the local marketplace copy had the plugin.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] "A refresh failure (e.g. another marketplace the user added is unreachable)" no longer applies: the refresh is now named, so another marketplace cannot make it fail. Describe the case the fallback is actually for, such as the plugin's own marketplace being unreachable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 02922d2: the comment now describes the plugin's own marketplace being unreachable while the local copy still lists the plugin.

}

func TestInstallPluginRollsBackMarketplaceOnRefreshFailure(t *testing.T) {
func TestInstallPluginContinuesWhenRefreshFails(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] TestInstallPluginContinuesWhenRefreshFails covers only a just-added marketplace, where the local copy is fresh by construction. The case the fallback exists for, a marketplace already registered whose refresh fails, is untested, and so is recordedMarketplace's fallback for a record with an empty Marketplace in pluginUpdateSteps. Add a case where plugin marketplace list returns the marketplace and the refresh fails: assert no marketplace remove and InstalledMarketplace == false. Add another where an update with PluginRecord{Marketplace: ""} refreshes the agent's marketplace.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 02922d2: added TestInstallPluginContinuesWhenRefreshOfRegisteredMarketplaceFails (marketplace list returns the marketplace, refresh fails: install runs, no marketplace remove, InstalledMarketplace == false, empty Version) and TestUpdatePluginForAgentClaudeRecordWithoutMarketplace (PluginRecord{Marketplace: ""} refreshes and updates against the agent's marketplace).

@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 3e0bf18 to 410f869 Compare November 11, 2026 16:52
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from feb1410 to 4eff713 Compare November 11, 2026 16:52
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 410f869 to 88362b5 Compare November 11, 2026 16:54
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch 2 times, most recently from 056b3e7 to 02922d2 Compare November 11, 2026 16:57
@anton-107 anton-107 changed the title aitools: refresh only the Databricks marketplace, and do not fail the install on it aitools: refresh only the plugin's own marketplace, and do not fail the install on it Nov 11, 2026
@anton-107

Copy link
Copy Markdown
Contributor Author

Comments outside the changed lines, both done in 02922d2:

  • plugin.go record version: when the refresh fails, InstallPluginForAgent now records an empty Version, so aitools update --check and aitools list show "version unknown" until the next successful update. The two refresh-failure tests assert it.
  • recordedInstallTarget now returns plugin + "@" + recordedMarketplace(agent, rec).

@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 88362b5 to 679dd3e Compare November 11, 2026 17:28
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 02922d2 to d467e3f Compare November 11, 2026 17:28
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 679dd3e to ca64d39 Compare November 11, 2026 19:28
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch 2 times, most recently from 4cc5453 to 605b293 Compare November 11, 2026 19:42
@anton-107

Copy link
Copy Markdown
Contributor Author

Answers to the two Questions in the review, with the change in 605b293:

1. Install and update treat a refresh failure differently. That difference is gone. aitools update now works like install: if refreshing the plugin's own marketplace fails (claude plugin marketplace update <name> or codex plugin marketplace upgrade <name>), it warns with the agent's stderr and still runs plugin update / plugin add from the local copy. The CLI can't read which version that copy holds, so the state records the plugin version as empty and aitools list / aitools update --check show "version unknown" until a later update refreshes it. If the update itself fails, the plugin is skipped as before, and the error includes the refresh error as well. Both paths share refreshMarketplace and withRefreshError in plugin.go. Tests: TestUpdatePluginForAgentContinuesWhenRefreshFails (Claude Code and Codex), TestUpdatePluginForAgentReportsRefreshWhenUpdateFails, TestUpdateInstalledPluginsRecordsUnknownVersionWhenRefreshFails.

2. Is the continue-on-refresh-failure path still needed? Yes, I kept it. A stale local copy that still lists the plugin installs a working plugin, so failing the whole run there blocks a usable install over a refresh that can be retried later. The run no longer hides that it fell back: it prints a warning that tells the user to run databricks aitools update later, and it records the version as unknown instead of claiming the latest one. If the local copy doesn't have the plugin, the install still fails, removes a marketplace it just added, and reports both errors.

@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from ca64d39 to 9d4b058 Compare November 11, 2026 19:59
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 605b293 to eb09c76 Compare November 11, 2026 19:59

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Automated review from simonfaltum's review agent (pr-review skill) at eb09c76. Each finding was verified against the code; simonfaltum may follow up.

Not approving yet: 1 open design question.

This PR names the marketplace in the Claude Code refresh and the Codex upgrade, so an unreachable third-party marketplace no longer breaks aitools install or aitools update. A failed refresh now warns, continues from the local copy, and records the version as unknown. Everything from the earlier review is addressed, CI is green, and nothing is blocking. The main remaining point is that the refresh warning fires before the install or update result, so a failed install shows wrong advice and repeats the refresh error. The rest are small nits, plus one question about whether to split the fail-soft change into its own PR.

Questions

  1. This PR makes two changes: naming the marketplace in the refresh, which fixes the reported failure on its own, and making a failed refresh non-fatal for install and update, with the version-unknown record. AGENTS.md asks to keep each PR focused on one change. You explained earlier why you kept the fallback. Would you move the fallback into its own PR, so the naming fix can land and be reverted on its own? Or do you prefer to keep them together, and if so, why?

Comments outside the changed lines

  • [nit] cmd/aitools/list.go:394: This PR now writes Version: "" after every failed refresh. agentStatusLabel uses version == "" to mean "nothing picked yet". Example: Claude Code is installed in both scopes and the project refresh fails, so project has "" and global has 0.2.7, with release 0.2.7. Project is visited first and is stale. Global then overwrites version, so the label reads "databricks plugin · v0.2.7 · update available": the up-to-date version shows next to "update available", and the unknown scope is hidden. Track the pick with a separate bool instead of version == "". This code is outside the diff, but the PR is what makes the case common.
  • [nit] .github/PULL_REQUEST_TEMPLATE.md: .agents/skills/pr-checklist/SKILL.md:48 asks agent-written PRs to follow .github/PULL_REQUEST_TEMPLATE.md exactly: ## Changes, ## Why, ## Tests, in that order, with no other sections. This body uses ## Summary, ## Evidence, and ## Merge Danger. It also opens with a duplicated "Stacked on" line. Rename the sections to the template headings and trim the Evidence block.

Comment thread libs/aitools/installer/plugin.go Outdated
}
_, err := runAgentCmd(ctx, pluginCmdTimeout, prepend(bin, args))
if err != nil {
log.Warnf(ctx, "Could not refresh the %s marketplace, using its local copy for the %s plugin; run `databricks aitools update` later: %s", marketplace, agent.DisplayName, stderrOf(err))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] refreshMarketplace prints "Could not refresh the X marketplace, using its local copy …; run databricks aitools update later" as soon as the refresh fails, before the install or update has run. If that command then fails, the warning is wrong, and the refresh stderr is printed a second time through withRefreshError in the "Skipped …" line. Example: a first aitools install while GitHub is unreachable. marketplace add fails (its error is ignored at line 291), the refresh fails, and the install fails. No record is saved, so aitools update never retries Claude Code: UpdateInstalledPlugins only walks recorded plugins, and migration needs raw skills. The warning still tells the user to run it. Have refreshMarketplace only run the command and return the error, and warn only after InstallPluginForAgent/UpdatePluginForAgent succeeds. The failure path already carries the refresh error in the BlockedError detail.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7123aae. refreshMarketplace now only runs the command and returns the error. The warning moved to warnLocalMarketplaceCopy, which InstallPluginForAgent and UpdatePluginForAgent call only after the install or update succeeds. On failure the refresh error appears once, in the BlockedError detail via withRefreshError, and there is no "run databricks aitools update later" advice.

}

func TestInstallPluginRollsBackMarketplaceOnRefreshFailure(t *testing.T) {
func TestInstallPluginContinuesWhenRefreshFails(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] On a successful install or update after a failed refresh, the warning is the only thing that tells the user they got the local copy. No test checks it, so it could be dropped or demoted to debug without any test failing. The package already captures logs (update_test.go:651-653 puts a slog.TextHandler on a buffer). Assert the message here and in the update equivalent. If the warning moves after the result, also assert that a failed install does not print it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7123aae. A captureWarnings helper puts a slog.TextHandler on a buffer. TestInstallPluginContinuesWhenRefreshFails (already registered case) and TestUpdatePluginForAgentContinuesWhenRefreshFails (Claude Code and Codex) assert the warning and the agent's stderr. TestInstallPluginRollsBackMarketplaceWhenRefreshAndInstallFail and TestUpdatePluginForAgentReportsRefreshWhenUpdateFails assert that a failed install or update does not print it. Demoting the warning to debug makes the two success tests fail.

}
rec.Version = version
if !refreshed {
rec.Version = ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] Codex has no manifest reader (pluginInstalls is nil), so InstalledTools uses only the recorded version, and recordedPluginVersions skips empty ones (detect.go:58). After codex plugin marketplace upgrade databricks-agent-skills fails and codex plugin add succeeds, the record goes from 0.2.6 to "". Every later CLI call then reports no codex_* pair, although the plugin is installed. Before this PR, the update was skipped and the old version stayed. If that's intended, a note in InstalledTools would help. Otherwise, keep the previous version for telemetry, or emit an explicit unknown token.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Kept the empty version and documented it in 7123aae: the InstalledTools doc comment now names this case (an agent without a plugin cache reader, such as Codex, whose last install or update ran from a marketplace copy that could not be refreshed). Keeping 0.2.6 would report a version the CLI can't vouch for: codex plugin add ran from the local copy, which may hold a newer or the same version. The empty record is also what makes aitools list and aitools version say "version unknown" for Codex. The next successful update records the version again. An explicit unknown token would change the <agent>_<version> token format, so I left that out of this PR.

Comment thread libs/aitools/installer/plugin.go Outdated
// After a failed refresh we can't tell which version the local copy held,
// so the record says "version unknown" until the next successful update.
version := DisplaySkillsVersion(ref)
if refreshErr != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] When installedMarketplace is true, marketplace add has just cloned the marketplace, so the local copy is current. A refresh failure right after that still records Version: "", and aitools update --check and aitools version then show "version unknown" for an up-to-date install (TestInstallPluginContinuesWhenRefreshFails asserts this). Either skip the refresh when we just added the marketplace, or clear the version only when refreshErr != nil && !installedMarketplace.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7123aae. When installedMarketplace is true, marketplace add just cloned the marketplace, so a failed refresh right after it neither clears the version nor prints the warning. The version is cleared and the warning printed only when refreshErr != nil && !installedMarketplace. TestInstallPluginContinuesWhenRefreshFails is now a table: "just added" records 0.2.6 with no warning, and "already registered" records an empty version with the warning. A failed install still carries the refresh error in its detail in both cases.

Comment thread libs/aitools/installer/plugin.go Outdated
// failed refresh is not fatal: the local copy of the marketplace usually still
// lists the plugin, so the caller goes on with it. The local copy may be out of
// date, so the caller records the plugin version as unknown.
func refreshMarketplace(ctx context.Context, agent *agents.Agent, bin, marketplace string, args []string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] The marketplace parameter is only used in the log line, and the same name is already inside args. Install passes agent.Plugin.Marketplace twice, and update calls recordedMarketplace both in pluginRefreshArgs and at the call site. The argv is also built in two places: marketplaceUpdateArgs for Claude and pluginRefreshArgs for Codex. A single marketplaceRefreshArgs(agent, marketplace) switch, called inside refreshMarketplace(ctx, agent, bin, marketplace), would serve install and update and remove pluginRefreshArgs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Partly done in 7123aae. refreshMarketplace(ctx, bin, args) no longer takes agent or marketplace. The marketplace name is used only in the success-path warning, so recordedMarketplace is called once per path. I kept two argv builders: install refreshes only for Claude Code (marketplaceUpdateArgs), while update refreshes for Claude Code and Codex (pluginRefreshArgs). A single switch used by both would add codex plugin marketplace upgrade to aitools install for Codex, which install has never run. That is a behavior change outside this PR.

Comment thread libs/aitools/installer/plugin_test.go Outdated
assert.NotContains(t, stub.Commands(), "claude plugin marketplace remove databricks-agent-skills")
}

func TestInstallPluginContinuesWhenRefreshOfRegisteredMarketplaceFails(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] .agents/rules/testing.md:51 says "Use table-driven tests for multiple similar cases". This test differs from TestInstallPluginContinuesWhenRefreshFails (line 166) only in the plugin marketplace list stdout and the InstalledMarketplace assertion. TestUpdatePluginForAgentClaudeRecordWithoutMarketplace (line 243) differs from TestUpdatePluginForAgentClaudeOneStep (line 230) only in rec.Marketplace. Fold each pair into a table, as TestUpdatePluginForAgentContinuesWhenRefreshFails already does.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7123aae. TestInstallPluginContinuesWhenRefreshFails is a table over "just added" and "already registered" (marketplace list output, InstalledMarketplace, version, warning). TestUpdatePluginForAgentClaudeOneStep is a table over a record with a marketplace and one without, and it asserts the exact command sequence.

@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from eb09c76 to 7123aae Compare November 11, 2026 22:58
@anton-107

Copy link
Copy Markdown
Contributor Author

Re-review at eb09c76, changes in 7123aae:

Question 1 (split the fail-soft path into its own PR?). I'd keep them together. Naming the marketplace stops an unrelated marketplace from failing the refresh. After that, the refresh fails only when the plugin's own marketplace can't be reached, for example network or GitHub trouble during aitools install. That is the same reported situation, and the fail-soft path is what makes it recoverable instead of a hard install failure. Both changes are small and touch the same few lines in plugin.go, so they revert together cleanly.

cmd/aitools/list.go:394. Fixed: agentStatusLabel tracks the pick with a picked bool, so an empty ("version unknown") scope counts as picked. In your example the label now reads "databricks plugin · version unknown · update available". Covered by TestAgentStatusLabelKeepsUnknownStaleScope, which fails on the old code.

PR template. I kept the body's layout, which the PRs in this stack share. The first line Stacked on #7472. is the form used across the stack, so it stays; the second line names the parent PR's title for readers. I updated the body's content to match the changes above.

@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 9d4b058 to 149db0e Compare November 11, 2026 23:37
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 7123aae to 66c0a4f Compare November 11, 2026 23:37
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 149db0e to 3edb030 Compare November 11, 2026 23:50
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 66c0a4f to b469e66 Compare November 11, 2026 23:50
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 3edb030 to 239e195 Compare November 11, 2026 23:58
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from b469e66 to 7982dd8 Compare November 11, 2026 23:58
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 239e195 to 9cf349f Compare November 12, 2026 00:06
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 7982dd8 to d50659f Compare November 12, 2026 00:06
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 9cf349f to 005fc18 Compare November 12, 2026 00:15
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from d50659f to efb3351 Compare November 12, 2026 00:15
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 005fc18 to f251254 Compare November 12, 2026 00:27
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from efb3351 to 0d74442 Compare November 12, 2026 00:27

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Automated review from simonfaltum's review agent (pr-review skill) at 0d74442. Each finding was verified against the code; simonfaltum may follow up.

Not approving yet: 1 open design question.

This PR names the marketplace in the Claude Code refresh and the Codex upgrade, so an unreachable third-party marketplace no longer breaks aitools install or aitools update. If the plugin's own marketplace can't be refreshed, the commands now continue from the local copy, warn, and record the version as unknown. The naming fix is correct: both real CLIs accept a marketplace name. Every earlier thread is addressed, CI is green, and nothing is blocking. One nice-to-have: a Codex-only user whose refresh failed during an update is told again to install Databricks skills by version --check. There are also three small nits, and an open question on whether the fail-soft path should stay bundled with the naming fix.

Questions

  1. Thanks for explaining why the fail-soft refresh stays in this PR. One thing to confirm before approving: when an already-registered marketplace can't be refreshed, aitools install and aitools update now exit 0 with a warning and record "version unknown", where before they failed with install-failed. Is the warning plus "version unknown" the signal you want in that case? Or would you rather land the marketplace-naming fix first and the fallback as a follow-up, so each can be reverted on its own?

Comments outside the changed lines

  • [nit] cmd/aitools/update.go:307: This PR makes an empty plugin version a common state. For it, printPluginCheckResults prints Claude Code databricks plugin version unknown (version unknown), because versionToken("") and the status are the same string. For the same record, aitools list says "update available", but the check output doesn't tell the user that running aitools update would help. Use a status such as "update may be available" for "", so the line reads databricks plugin version unknown (update may be available), and cover it in a test.

Comment thread libs/aitools/installer/detect.go Outdated
Comment on lines +16 to +18
// omitted rather than reported without one. That includes an agent without a
// plugin cache reader (Codex) whose record has no version because its last
// install or update ran from a marketplace copy that could not be refreshed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] Codex has no plugin cache reader, so MaybeVersionCheckTip relies on InstalledTools to know that a Codex plugin is installed (see its doc in libs/aitools/agents/recommend.go). recordedPluginVersions skips empty versions. Take a Codex-only user whose codex plugin marketplace upgrade databricks-agent-skills fails during aitools update while codex plugin add succeeds. The record becomes "", and from then on every databricks version --check prints "Tip: coding agents detected without Databricks skills. Run 'databricks aitools install'", even though the plugin is installed. The codex_* user-agent pair also disappears, which the earlier thread covered. Make the tip check look at whether a plugin record exists rather than whether a version is recorded. For example, pass a function that lists the recorded plugin agents whatever their version, or have InstalledTools emit an unknown version for them. A smaller point: Codex install never runs a refresh (marketplaceUpdateArgs is nil for Codex), so for Codex, "install or update" in this comment can only mean update.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 40e1da8. New installer.InstalledPluginAgents lists the agents that have the plugin in their own cache or recorded in the CLI install state, whether or not a version is known. databricks version --check now passes it to MaybeVersionCheckTip instead of InstalledTools, so a Codex-only user whose record has no version doesn't get the install tip. InstalledTools and its <agent>_<version> tokens stay the same. recordedPluginVersions now keeps agents whose record has no version, but a scope without a version never replaces one that has a version. Tests: TestInstalledPluginAgents and two new TestInstalledTools cases. You're right about Codex install never refreshing, so the InstalledTools comment now says "update".

Comment thread libs/aitools/installer/update.go Outdated
Comment on lines +471 to +474
rec.Version = version
if !refreshed {
rec.Version = ""
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] When the marketplace refresh fails, this clears the version even if the record already holds the current release. For example, Claude Code is recorded at 0.2.7, the release is 0.2.7, and aitools update runs while GitHub is flaky. The refresh fails, and claude plugin update succeeds without changing anything. The record then becomes "", so aitools list shows "version unknown · update available" and update --check shows "version unknown" for a plugin that is up to date. Before this PR the record stayed at 0.2.7. The local copy can only have moved forward since the refresh that wrote that record, so keeping it is safe. Clear the version only when !refreshed && rec.Version != version, and add that case to TestUpdateInstalledPluginsRecordsUnknownVersionWhenRefreshFails.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 40e1da8. UpdateInstalledPlugins clears the version only when the refresh failed and the record doesn't already hold this release (refreshed || rec.Version == version keeps it). TestUpdateInstalledPluginsRecordsUnknownVersionWhenRefreshFails is now a table: 0.2.6 recorded becomes "", and 0.2.7 recorded stays 0.2.7.

return PluginRecord{}, &BlockedError{Agent: agent.Name, Reason: ReasonInstallFailed, Detail: stderrOf(err)}
}
}
refreshErr := refreshMarketplace(ctx, bin, marketplaceUpdateArgs(agent, agent.Plugin.Marketplace))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] The Merge Danger section says "No extra network waits". Before this PR, a refresh that hit the 60s pluginCmdTimeout ended the run. Now the install (line 301) or the update runs next and can wait up to another 60s. On a machine where GitHub hangs instead of failing fast, aitools install for Claude Code can take about a minute longer than before. Please correct that sentence in the description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Corrected in the description. Merge Danger now says that a refresh which hangs until the 60s timeout is followed by the install or update, which can wait up to another 60s. So aitools install or aitools update can take about a minute longer when GitHub hangs instead of failing fast.

@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 0d74442 to 40e1da8 Compare November 12, 2026 09:19
@anton-107

Copy link
Copy Markdown
Contributor Author

Third review at 0d74442, changes in 40e1da8:

Question 1. Yes. When an already-registered marketplace can't be refreshed, the warning plus "version unknown" is the signal I want. A stale local copy that still lists the plugin installs a working plugin. The warning names the marketplace, shows the agent's error and says to run databricks aitools update later, and "version unknown" keeps aitools list and update --check from reporting it as current. I'd keep the fallback in this PR for the reason I gave earlier: once the refresh is named, the fallback is what makes its failure recoverable, and the two changes are small and revert together cleanly.

cmd/aitools/update.go:307. Fixed: printPluginCheckResults prints "update may be available" for a record without a version, so the line reads databricks plugin version unknown (update may be available). TestUpdateCheckPrintsPluginState is now a table over current, older and unknown versions.

@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch 2 times, most recently from 3e979d0 to 99db1f7 Compare November 12, 2026 09:57
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 40e1da8 to c2eaa89 Compare November 12, 2026 10:01
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 99db1f7 to 07cf845 Compare November 12, 2026 10:20
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from c2eaa89 to 20e3ef4 Compare November 12, 2026 10:20
… install on it

Claude Code's `plugin marketplace update` with no name refreshes every marketplace the user has added, so one unreachable third-party marketplace failed the Databricks plugin install. The refresh now names the marketplace the Databricks plugin is listed in, and a failed refresh no longer stops the install or the update: both go on from the local copy of the marketplace and, once that succeeds, warn and record the plugin version as unknown. Codex's `plugin marketplace upgrade` in `aitools update` names the marketplace too.

```diff
 InstallPluginForAgent (libs/aitools/installer/plugin.go)
   marketplace add (if absent)
-  claude plugin marketplace update
-    on error: remove the marketplace we added, return install-failed
+  claude plugin marketplace update <marketplace>
+    on error: keep the error, continue
   claude plugin install databricks@<marketplace>
     on error: remove the marketplace we added
-    return install-failed with the install error
+    return install-failed with the install error
+      + "(refreshing the marketplace also failed: <refresh error>)" if the refresh failed
-  record Version = <ref>
+  if the refresh failed and the marketplace was not just added by us:
+    warn that the plugin came from the local copy (with the agent's stderr)
+    record Version = "" (version unknown)
+  else record Version = <ref>

 UpdatePluginForAgent (aitools update)
-  claude plugin marketplace update
+  claude plugin marketplace update <recorded marketplace, or the agent's current one>
-    on error: return install-failed
+    on error: keep the error, continue
   claude plugin update ...
-  codex plugin marketplace upgrade
+  codex plugin marketplace upgrade <recorded marketplace, or the agent's current one>
+    on error: same as above
   codex plugin add ...
   on update error: return install-failed
+    + "(refreshing the marketplace also failed: <refresh error>)" if the refresh failed
-  record Version = <ref>
+  if the refresh failed: warn as above, and record Version = "" (version unknown)
+    unless the record already holds <ref>
+  else record Version = <ref>
```

A record without a version still counts as an installed plugin for the `databricks version --check` tip (`InstalledPluginAgents`), and `aitools update --check` shows it as "version unknown (update may be available)".

Changelog fragment: `.nextchanges/cli/aitools-marketplace-refresh.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
@anton-107
anton-107 force-pushed the aitools/plugin-probe-timeout branch from 07cf845 to d82eb6b Compare November 12, 2026 10:24
@anton-107
anton-107 force-pushed the aitools/databricks-marketplace-refresh branch from 20e3ef4 to 06fd311 Compare November 12, 2026 10:24

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Approval from simonfaltum's review agent at 06fd311, on Simon's decision.

The design questions from the earlier automated reviews no longer block this PR: your answers are accepted, and any still-open ones are non-blocking. The changes since the last automated review (0d74442 → 06fd311) address its comments.

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

ai-auto PRs made by AI loops & factories,

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants