Skip to content

aitools: install skill files when a detected agent's CLI cannot run - #7482

Open
anton-107 wants to merge 16 commits into
aitools/nothing-installed-failsfrom
aitools/skills-fallback-for-detected-agents
Open

anton-107 wants to merge 16 commits into
aitools/nothing-installed-failsfrom
aitools/skills-fallback-for-detected-agents

Conversation

@anton-107

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

Copy link
Copy Markdown
Contributor

Stacked on #7481.

Changes

databricks aitools install now gives raw skill files to a detected coding agent whose CLI is not on PATH (for example a Claude Code desktop or IDE install: ~/.claude exists, claude does not), unless the agent already has the plugin. Before, the run printed "Skipped Claude Code" and left the agent with nothing.

The plan decides this, so the picker label and what the run does agree, and the agent joins the run's one skills batch (one ref lookup, one manifest fetch):

 planItemFor (plugin agent, scope supported)
-  plugin
+  if CLI not on PATH, the agent is detected (config dir), and not named via --agents
+    if the agent already has the plugin for this scope
+      keep it (plugin delivery, nothing installed; outcome "kept")
+    else
+      skill files instead (skills delivery)
+  else
+    plugin
  • Already has the plugin: Claude Code's own installed_plugins.json decides, because claude plugin uninstall leaves the CLI's record behind. When that file exists but cannot be read, the CLI's install state for the scope decides, as it does for the other agents. A global install counts only a machine-wide plugin. A project-scope install counts only a project-scope plugin of this project: its skill files are shared through the repository, while a user or local plugin is personal.
  • Known limitation (Codex CLI, GitHub Copilot): the CLI cannot read these agents' own plugin lists, so only a plugin installed by databricks aitools is known. If one of them got the plugin another way (for example through its app or IDE) and its CLI is not on PATH, the fallback adds skill files next to the plugin and every skill shows twice. install --help says so too.
  • Kept plugin: install prints how to refresh it and, before the skills batch, removes the raw skills an earlier run gave the agent, as a plugin install does. The picker labels it "plugin installed · CLI not found".
  • Skill files instead: install prints how to switch to the plugin. After the skills install it drops a stale plugin record from the CLI's install state; a failure to do so is a warning.
  • Project plugin next to a global fallback: a global-scope fallback still runs in a directory where the agent has a project or local databricks plugin, since the global skill files serve every other project, and it warns that this project will see both the plugin and the global skill files.
  • Exit code and telemetry: a kept plugin counts like an installed one. The run exits 0 with outcome SUCCESS, or NOTHING_TO_DO when it did nothing else.
  • Unchanged: agents named via --agents, and agents whose CLI is on PATH but whose plugin command fails, still fail with the exact commands to run.
  • aitools update: no longer tries, and fails, to migrate an agent whose CLI is not on PATH from raw skills to the plugin; it keeps refreshing its raw skills. It also no longer writes raw skills to an agent whose own manifest shows the plugin for the scope, since install keeps such a plugin without a CLI record: it removes them instead, and skips the reconcile when no agent is left that can take file skills in the scope.
  • Helpers: installer.PluginRecorded (now also used for the User-Agent tooling), installer.ForgetPluginInstalls, agents.Agent.HasPluginManifestReader, and an exported agents.Agent.DatabricksPluginActiveIn, which returns an error for an unreadable manifest.

Why

Desktop and IDE installs of Claude Code have the config dir but no claude CLI, so a plain databricks aitools install set nothing up for them. Skill files are what such an agent can use today, and a later run with the CLI on PATH switches it to the plugin. An agent that already has the plugin keeps it, because skill files next to the plugin would list every skill twice.

Tests

  • New acceptance test acceptance/experimental/aitools/skills/install-non-interactive: the detected-agent fallback in one skills batch, a kept plugin that removes the earlier skill files and an aitools update --force that does not write them back, the named-agent failure and its --skills-only recovery, a project-scope fallback that a later user-scope plugin does not stop aitools update --scope=project from refreshing, and a project-scope run that sets up nothing exiting 1.
  • Unit tests (table-driven) for the plan decision and picker label, executePlan for both cases, the funnel outcomes, the scope rules and unreadable manifest in pluginAlreadyInstalled, the project-plugin warning, ForgetPluginInstalls / PluginRecorded, the manifest reader, the project-scope plugin rule (DatabricksProjectPluginIn, excludePluginAgents), the update migration skip, and a project-scope update whose only project-capable agent has the plugin.
  • ./task test-exp-aitools, ./task lint-q, ./task checks pass locally on Linux. Not run on Windows or macOS.

Changelog fragment: .nextchanges/cli/aitools-install-skills-fallback.md.

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:28
@anton-107 anton-107 added the ai-auto PRs made by AI loops & factories, label Nov 11, 2026
@anton-107
anton-107 requested review from a team as code owners November 11, 2026 14:28
@anton-107
anton-107 requested a review from misha-db November 11, 2026 14:28
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 54c4bc5 to 75d4a60 Compare November 11, 2026 14:34
@anton-107
anton-107 requested a review from a team as a code owner November 11, 2026 14:34
@anton-107
anton-107 requested a review from rauchy November 11, 2026 14:34
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 5afe64b to d5fb33f Compare November 11, 2026 14:34
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Commit: eae06626f | Workflow run

Regression test report

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

16 tests: regression=12 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/experimental/aitools/skills/install-non-interactive ✅ ❌ ❌
TestAccept/experimental/aitools/skills/install-output-json-agent-error ✅ ❌ ❌
TestAccept/experimental/aitools/skills/install-output-json-agents-skipped ✅ ❌ ❌
TestAccept/experimental/aitools/skills/install-output-json-error ✅ ❌ ❌
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:42707" ("[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-metadata3777012594/002/output.txt

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:39121" ("[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-metadata2944254945/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:34321" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:34321" ("[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-tip1691592597/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:38477" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:38477" ("[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-tip1383004096/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:45935" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:45935" ("[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/TestAcceptcmdaitoolstelemetryhint848940930/002/output.txt

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43709" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43709" ("[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/TestAcceptcmdaitoolstelemetryhint371566749/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:40871" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40871" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall1861843318/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\":9,\"exit_code\":0},\"aitools_install_event\":{\"agents\":[\"CLAUDE_CODE\"],\"scope\":\"GLOBAL\",\"error_category\":\"TYPE_UNSPECIFIED\"}}}}"
        +    ]
        +  }
        +}
         
         >>> jq -r .tokens["test-profile"].access_token [TEST_TMP_DIR]/home/.databricks/token-cache.json
        -expired-access-token
        +oauth-token

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:33499" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:33499" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall697508395/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\":5,\"exit_code\":0},\"aitools_install_event\":{\"agents\":[\"CLAUDE_CODE\"],\"scope\":\"GLOBAL\",\"error_category\":\"TYPE_UNSPECIFIED\"}}}}"
        +    ]
        +  }
        +}
         
         >>> jq -r .tokens["test-profile"].access_token [TEST_TMP_DIR]/home/.databricks/token-cache.json
        -expired-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:37987" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:37987" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure1534469174/002/output.txt
        @@ -6,15 +6,8 @@
         Using skills version test-ref
         Fetching skills manifest...
         Error: skill "nonexistent" not found
        -Run 'databricks aitools list' to see the available 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":"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
        @@ -24,15 +15,8 @@
         Using skills version missing-ref
         Fetching skills manifest...
         Error: skills manifest at databricks-agent-skills@missing-ref: not found
        -No skills release exists at the version DATABRICKS_SKILLS_REF names. Unset it, or set it to a release tag from https://github.com/databricks/databricks-agent-skills/releases, then re-run 'databricks aitools install --scope=global --agents=cursor --skills-only'
         
         >>> 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 SKILLS_FETCH_FAILED
        @@ -42,45 +24,24 @@
         Using skills version broken-ref
         Fetching skills manifest...
         Error: failed to fetch manifest: HTTP 500
        -[DATABRICKS_HOST] returned HTTP 500, a server-side error. Wait a few minutes and re-run 'databricks aitools install --scope=global --agents=cursor --skills-only'
         
         >>> 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":"SKILLS_FETCH_FAILED","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":"SKILLS_FETCH_FAILED"}}
        +{"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
         
         >>> [CLI] aitools install --skills-only --scope=project --agents=cursor
         Skipped Cursor: does not support project-scoped skills
        -  To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only
         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"}]}}
         
         === a detected agent skipped for the scope prints its recovery step and exits 1 with nothing installed
         
         >>> [CLI] aitools install --skills-only --scope=project
         Skipped Cursor: does not support project-scoped skills
        -  To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only
        -Error: nothing was installed; follow the steps above
         
        ->>> 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"}]}}
        +Unexpected success
        +
        +Exit code: 1

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:44769" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:44769" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure3586016607/002/output.txt
        @@ -6,15 +6,8 @@
         Using skills version test-ref
         Fetching skills manifest...
         Error: skill "nonexistent" not found
        -Run 'databricks aitools list' to see the available 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":"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
        @@ -24,15 +15,8 @@
         Using skills version missing-ref
         Fetching skills manifest...
         Error: skills manifest at databricks-agent-skills@missing-ref: not found
        -No skills release exists at the version DATABRICKS_SKILLS_REF names. Unset it, or set it to a release tag from https://github.com/databricks/databricks-agent-skills/releases, then re-run 'databricks aitools install --scope=global --agents=cursor --skills-only'
         
         >>> 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 SKILLS_FETCH_FAILED
        @@ -42,45 +24,24 @@
         Using skills version broken-ref
         Fetching skills manifest...
         Error: failed to fetch manifest: HTTP 500
        -[DATABRICKS_HOST] returned HTTP 500, a server-side error. Wait a few minutes and re-run 'databricks aitools install --scope=global --agents=cursor --skills-only'
         
         >>> 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":"SKILLS_FETCH_FAILED","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":"SKILLS_FETCH_FAILED"}}
        +{"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
         
         >>> [CLI] aitools install --skills-only --scope=project --agents=cursor
         Skipped Cursor: does not support project-scoped skills
        -  To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only
         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"}]}}
         
         === a detected agent skipped for the scope prints its recovery step and exits 1 with nothing installed
         
         >>> [CLI] aitools install --skills-only --scope=project
         Skipped Cursor: does not support project-scoped skills
        -  To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only
        -Error: nothing was installed; follow the steps above
         
        ->>> 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"}]}}
        +Unexpected success
        +
        +Exit code: 1
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:39021" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:39021" ("[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-uninstall1951457331/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:41059" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:41059" ("[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-uninstall3006799300/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"}}
FAIL

6 more failure details omitted, see the full report in the run.

@eng-dev-ecosystem-bot

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

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: eae0662

Run: 43197345550

Env 💚​RECOVERED ✅​pass 🙈​skip Time
💚​ aws linux-2core-8gb 1 270 31 10:20
💚​ aws-windows-latest-4core-16gb 1 272 29 10:24
💚​ azure linux-2core-8gb 1 269 31 8:56
💚​ azure-windows-latest-4core-16gb 1 271 29 8:58
💚​ gcp linux-2core-8gb 1 270 31 11:46
💚​ gcp-windows-latest-4core-16gb 1 272 29 13:14
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
Top 9 slowest tests (at least 2 minutes):
duration env testname
4:00 azure-windows-latest-4core-16gb TestAccept
3:55 aws-windows-latest-4core-16gb TestAccept
3:47 aws linux-2core-8gb TestAccept
3:46 azure linux-2core-8gb TestAccept
3:44 gcp-windows-latest-4core-16gb TestAccept
3:43 gcp linux-2core-8gb TestAccept
2:47 gcp-windows-latest-4core-16gb TestFilerReadWrite/dbfs
2:35 gcp-windows-latest-4core-16gb TestFsCpDirToDirWithOverwriteFlag/dbfs_to_dbfs
2:35 gcp-windows-latest-4core-16gb TestFilerRecursiveDelete/dbfs

@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 75d4a60 to 7d9546e Compare November 11, 2026 16:53
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from d5fb33f to 6dfcf53 Compare November 11, 2026 16:54
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 7d9546e to a2b82bc Compare November 11, 2026 16:55
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 6dfcf53 to 0de8029 Compare November 11, 2026 16:55
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from a2b82bc to 0758e7e Compare November 11, 2026 16:58
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 0de8029 to 3e2f9ef Compare November 11, 2026 16:58
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 0758e7e to 3f574d9 Compare November 11, 2026 17: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 3e2f9ef. Each finding was verified against the code; simonfaltum may follow up.

Not approving yet: 2 blocking issue(s), tagged [blocking]; 3 open design questions.

This PR makes databricks aitools install install skill files for a detected agent whose CLI cannot run, keep an existing plugin instead of adding duplicate skills, and drop stale plugin records after a fallback. The approach is reasonable, and CI passes. Two things block merging. First, the branch is built on an older revision of #7481, so it conflicts with its base and the kept-plugin logic has to be redone on top of anyInstalled/errNoAgentSetUp. Second, fallback agents are installed in a separate skills batch, so they keep stale skill copies after an upgrade. The other comments cover edge cases (kept-path cleanup, unreadable manifest, update warnings) and doc, test and description tidy-ups; three questions on the telemetry and fallback design are open.

Questions

  1. When a detected agent keeps its plugin and another agent's install fails, the run exits 0 but the funnel records FAILURE. An installed agent plus the same failure records SUCCESS. The current #7481 removed the exit-0-but-FAILURE branch, and per-agent results for kept or fallback agents carry the same CLI_NOT_ON_PATH entry as a real failure. Should kept be treated like installed (SUCCESS, or NOTHING_TO_DO when nothing else happened), or is the difference intended? If it is intended, could the per-agent telemetry distinguish 'kept' and 'fell back to skills' from 'failed'?
  2. The fallback is decided at run time inside executePlan. That is why the picker label, plan summary and outcomes disagree, and why fallback agents need a second skills install. Would deciding it at plan time in planItemFor work instead, for non-explicit plugin agents whose CLI is missing (StateInstalledCLIMissing)? That puts them in the single skills batch and keeps the summary accurate; the run-time path would only be needed when the binary is present but 'plugin --help' fails. Or is there a reason to keep it at run time?
  3. A global-scope fallback ignores a project or local Claude plugin that applies to the current directory, so that project ends up with both the plugin and global raw skills. Is accepting duplicates in that project the intended trade-off? The alternatives are skipping the fallback, or printing a warning when the cwd has a project-scope plugin.

Comments outside the changed lines

  • [nit] cmd/aitools/install.go:673: If recordPluginInstallsFn fails, executePlan returns before the fallback block. The fallback agents get nothing, even though "Installing skill files instead" was already printed for them. Run the fallback before the early return, or fold it into a single skills batch.
  • [nit] .github/PULL_REQUEST_TEMPLATE.md: The repo's pr-checklist asks for the template sections ## Changes, ## Why and ## Tests, with no invented sections, and an agent disclosure as a single italic line. The description uses ## Summary, ## Evidence and ## Merge Danger. Its claim "No extra network calls beyond the existing skills manifest fetch" is also inaccurate: the fallback fetches the manifest a second time. Please restructure the description and correct that claim.

Comment thread cmd/aitools/install.go Outdated
case runErr == nil:
if category, ok := nothingInstalledCategory(outcomes); ok {
funnel.finishWith(ctx, protos.AitoolsCommandOutcomeFailure, category)
} else if onlyKeptPlugins(outcomes) {

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.

[blocking] This branch sits on an older revision of #7481 (0758e7e). #7481 has since been force-pushed to 3f574d9, the base. GitHub reports this PR as not mergeable, and even a rebase of just this PR's commit conflicts in install.go, telemetry.go and funnel_test.go. The current #7481 removed nothingInstalledCategory, newNothingInstalledError and nothingInstalledError, and runErr == nil now always records SUCCESS. The kept-plugin logic here is written against those removed helpers. If the conflicts are resolved by taking the base's anyInstalled (which only checks outcomeInstalled), a kept-only run returns errNoAgentSetUp and exits 1. If the base's case runErr == nil is taken, the NOTHING_TO_DO outcome is lost. Please rebase onto the current aitools/nothing-installed-fails, count outcomeKept in anyInstalled, and redo the kept-plugin funnel handling on top of the base's runErr == nil branch.

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.

Rebased onto the current aitools/nothing-installed-fails (46180b9) in 7137e09. anyInstalled now counts outcomeKept, so a run whose only agent kept its plugin exits 0. The kept-plugin funnel handling sits in the base's runErr == nil branch through keptPluginsFinish: NOTHING_TO_DO when the run only kept plugins, FAILURE with the failed agent's category when it only kept plugins next to a failed install, SUCCESS when anything was installed. These outcomes are the same as in the previous revision.

Comment thread cmd/aitools/install.go Outdated
if !quiet {
installer.PrintInstallingFor(ctx, fallbackAgents)
}
if err := installSkillsForAgentsFn(ctx, src, fallbackAgents, opts); err != 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.

[blocking] Fallback agents are installed by a second installSkillsForAgentsFn call. At global scope a one-agent batch copies files instead of symlinking (useSymlinks = len(detectedAgents) > 1), so Cursor and Claude Code each get their own copy. On a later install with a newer skill version, the Cursor batch updates the canonical files and sets state.Skills to the new version. The fallback batch then sees a matching version and an existing Claude copy (allAgentsHaveSkill only checks that the copy exists), so it skips Claude. The command succeeds while Claude keeps the old files, on every upgrade, because the skills batch always runs first. Two batches also resolve the ref and fetch the manifest twice and print the install banner twice, as the new acceptance output shows, which contradicts the description's "no extra network calls". Please install the fallback agents in the same InstallSkillsForAgents call as the other skills agents: run the plugin loop first, or decide the fallback at plan time.

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.

Fixed in 7137e09. The fallback is decided in planItemFor, so these agents are deliverySkills items in the run's single InstallSkillsForAgents call: symlinks when there are several agents, one ref lookup, one manifest fetch, one banner (see the acceptance output: "Installing Databricks skills for Claude Code, Cursor..."). The second batch is gone.

Comment thread cmd/aitools/install.go Outdated
cmdio.LogString(ctx, fmt.Sprintf("Installing databricks plugin for %s...", it.agent.DisplayName))
}
rec, err := installPluginForAgentFn(ctx, it.agent, it.scope, ref)
if err != nil && fallsBackToSkills(it, err) && pluginAlreadyInstalledFn(ctx, it.agent, opts.Scope) {

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 kept branch exists to avoid duplicate skills, but it never calls cleanupLegacyFn, so raw skills written by an earlier fallback stay next to the plugin. Scenario: run 1 has no claude on PATH, falls back, and drops the plugin record. The user then installs the plugin through Claude directly. Run 2, again without claude on PATH, keeps the plugin and leaves ~/.claude/skills alone, so Claude lists every skill twice. With no record, aitools update keeps refreshing the raw files. Please call cleanupLegacyFn(ctx, it.agent, opts.Scope) in the kept branch too, as the plugin-install path 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 7137e09: the kept path calls cleanupLegacyFn(ctx, it.agent, opts.Scope) and logs a failure at debug level, as the plugin path does. The acceptance test now runs your scenario: the first run falls back, then a Claude manifest with the plugin appears, and the second run keeps the plugin and removes Claude Code's skill files.

Comment thread cmd/aitools/recovery.go Outdated
project = cwd
}
}
return a.DatabricksPluginActiveIn(ctx, project)

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] claudePluginInstalls returns (nil, false) on any read or parse error, and DatabricksPluginActiveIn discards the ok. So pluginAlreadyInstalled returns false, the run falls back, and ForgetPluginInstalls removes a valid claude-code plugin record. If installed_plugins.json is mid-write or its schema changes, every non-interactive install without claude on PATH writes raw skills next to the installed plugin and drops the record. Please tell "no manifest file" apart from "manifest present but unreadable". In the unreadable case, fall back to the CLI state record, or keep the plugin.

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 7137e09. The manifest reader now returns an error when installed_plugins.json exists but cannot be read or parsed (a missing file is still "not installed"), and DatabricksPluginActiveIn returns it. pluginAlreadyInstalled then uses the CLI's state record for the scope, so an unreadable manifest no longer drops a valid record. DatabricksTooling keeps treating a read error as not installed.

Comment thread cmd/aitools/install.go Outdated
for _, a := range fallbackAgents {
outcomes = append(outcomes, agentOutcome{agent: a, delivery: deliverySkills, status: outcomeInstalled})
}
// These agents have no plugin (pluginAlreadyInstalled said so), so a

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] A fallback agent ends up with raw skills and no plugin record, which makes it a plugin-migration candidate in update (pluginMigrationCandidatesForScope). Every aitools update then calls the plugin install, fails, and prints Skipped Claude Code: claude-code: cli-not-on-path: … (the raw BlockedError). Before this PR only --skills-only users reached that state; now desktop and IDE users reach it by default. Consider skipping the migration attempt when the CLI is not on PATH, or printing the same friendly reason and recovery that install prints.

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 7137e09: pluginMigrationCandidatesForScope skips an agent whose CLI is not on PATH, matching install, so aitools update keeps refreshing its raw skills without a failing plugin install on every run. Covered by TestPluginMigrationCandidatesNeedTheCLI.

Comment thread cmd/aitools/recovery.go Outdated
return false
}
blocked, ok := errors.AsType[*installer.BlockedError](err)
return ok && blocked.Reason == installer.ReasonCLINotOnPath && !blocked.TimedOut

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] probePluginCLI returns ReasonCLINotOnPath both when the binary is missing and when it is on PATH but plugin --help fails, for example an old CLI or a probe false negative. fallsBackToSkills doesn't tell these apart, so an on-PATH CLI that is too old now gets raw skills instead of the "update your CLI" failure. The doc comment and PR description both say "not on PATH". Either check !it.agent.HasBinary(ctx) or update the comment and 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.

Fixed in 7137e09: the fallback now checks !a.HasBinary(ctx) when the run is planned, so only a missing binary falls back. A CLI on PATH whose plugin --help fails (too old, or a probe false negative) gets the "Install or update the ... CLI" failure, as before this PR. The description says so.

Comment thread cmd/aitools/funnel_test.go Outdated

// A kept plugin does not hide another detected agent's failed install: the
// funnel reports the failure with that agent's category.
func TestInstallFunnelKeptPluginWithAFailedInstallIsFailure(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] TestInstallFunnelKeptPluginIsNothingToDo and TestInstallFunnelKeptPluginWithAFailedInstallIsFailure repeat the setup of TestInstallFunnelDetectedPluginFailureIsNotSuccess. They differ only in the PATH binaries, the installer stub and the expected funnel shape. The same goes for TestExecutePlanFallsBackToSkillsWhenCLIIsMissing and TestExecutePlanKeepsAnInstalledPluginInsteadOfAddingSkills in install_test.go. .agents/rules/testing.md asks for table-driven tests for similar cases.

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 7137e09: TestInstallFunnelDetectedPluginAgents is one table (the only install failed, skill files instead, plugin kept, plugin kept next to a failed install), and TestExecutePlanWhenTheCLIIsMissing covers the fallback and kept cases in one table.

Comment thread cmd/aitools/install.go Outdated
})
continue
}
if err != nil && fallsBackToSkills(it, 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.

[nit] err != nil && fallsBackToSkills(it, err) is checked at line 609 and again here, followed by a third if err != nil branch for the same decision. One switch, or a single if with an inner already-installed / not-installed branch, would read more clearly (style-guide-go prefers switch for one decision).

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.

No longer applies in 7137e09: with the decision in planItemFor, the plugin loop in executePlan is back to its single if err != nil branch, and planItemFor makes the decision in one switch (scope unsupported, CLI missing, plugin).

// from the state file for the given CLI scope, for agents that no longer have
// the plugin. Agents without a record, or a scope without state, are left as
// they are.
func ForgetPluginInstalls(ctx context.Context, cliScope string, agentNames []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] ForgetPluginInstalls has no test in plugin_test.go. It is only exercised indirectly from cmd/aitools, so its no-state and no-record paths are untested. fallsBackToSkills also has no direct test: nothing checks that a TimedOut CLI_NOT_ON_PATH error or an explicit agent does not fall back. The kept notice (pluginKeptText) has no acceptance coverage either. Please add a small table test for fallsBackToSkills in recovery_test.go and a ForgetPluginInstalls test in plugin_test.go.

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.

Added in 7137e09: TestForgetPluginInstalls in plugin_test.go (no state, an agent without a record, a recorded agent; it also checks PluginRecorded per scope). fallsBackToSkills is gone; the plan decision that replaces it is covered by the table test TestPlanItemForAgentWithoutCLI (CLI on PATH, CLI missing, missing with the plugin, named via --agents, scope unsupported). A timeout can no longer lead to a fallback, because planning does not run the CLI. The kept notice is now in the acceptance output.

@@ -0,0 +1 @@
* `databricks aitools install` installs skill files for a detected coding agent whose CLI cannot run (for example a Claude Code desktop or IDE install), and says how to switch to the plugin later. ([#7482](https://github.com/databricks/cli/pull/7482))

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 pr-checklist says a fragment that leads with the command should use the present tense with "now". Suggest: "databricks aitools install now installs skill files for a detected coding agent whose CLI cannot run …".

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 7137e09: "databricks aitools install now installs skill files for a detected coding agent whose CLI is not on PATH (for example a Claude Code desktop or IDE install), unless it already has the plugin, and says how to switch to the plugin later." The PR link is kept.

@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 3f574d9 to 46180b9 Compare November 11, 2026 19:33
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 3e2f9ef to 7137e09 Compare November 11, 2026 19:48
@anton-107

Copy link
Copy Markdown
Contributor Author

Addressed in 7137e09:

  • cmd/aitools/install.go:673 (early return when recordPluginInstallsFn fails): fallback agents are now part of the skills batch, which runs before the plugin loop, so a record failure no longer leaves them without skills.
  • Description: restructured into Changes / Why / Tests, and corrected the network claim: with a single skills batch there is one ref lookup and one manifest fetch. The attribution line keeps its standard wording.
  • Question 2: moved to plan time. planItemFor gives skill files (or keeps an installed plugin) to a detected plugin agent whose binary is not on PATH, which covers StateInstalledCLIMissing. I did not keep a run-time path for a binary on PATH whose plugin --help fails: such an agent fails with the "install or update the CLI" recovery, as before this PR, so there is no second skills batch at all and the picker label, plan summary and outcomes agree.

@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 7137e09 to 4fd042b Compare November 11, 2026 20:02
@anton-107

Copy link
Copy Markdown
Contributor Author

On Question 3 (global-scope fallback next to a project or local Claude plugin), in 4fd042b: the fallback still runs, because the global skill files serve every other project and the run cannot tell which projects the user works in. When the current directory has a project or local databricks plugin for that agent, install now also warns: "Claude Code has the databricks plugin for this project, so this project will see both the plugin and the global skill files." The check reads Claude Code's own manifest for the current directory; an unreadable manifest prints no warning. Covered in TestPluginAlreadyInstalledRespectsTheScope (project and local records of this project warn; a machine-wide record keeps the plugin instead; another project's record does not warn) and TestExecutePlanWhenTheCLIIsMissing.

@anton-107

Copy link
Copy Markdown
Contributor Author

On Question 1, in 4fd042b: a kept plugin now counts like an installed one. A run that sets up an agent by keeping its plugin finishes with SUCCESS, or with NOTHING_TO_DO when keeping plugins was all it did. A kept plugin next to another agent's failed install is now SUCCESS, the same as an installed agent next to that failure (it was FAILURE before). Covered by TestInstallFunnelDetectedPluginAgents and TestAnyInstalledAndFirstFailureCategory.

The per-agent results keep the CLI_NOT_ON_PATH entry for kept and fell-back agents in this PR. Telling them apart from a failed install in the per-agent results can follow in a separate PR.

@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 46180b9 to 544ae4b Compare November 11, 2026 21:11
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 4fd042b to 5261133 Compare November 11, 2026 21:31

@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 5261133. Each finding was verified against the code; simonfaltum may follow up.

Not approving yet: 1 open design question.

This PR makes databricks aitools install give skill files to a detected agent whose CLI isn't on PATH, and an agent that already has the plugin keeps it. It also drops stale plugin records and stops update from retrying the plugin migration for such agents. Every point from the earlier review round is addressed at this head, and no CI check has failed. Two non-blocking issues remain. Plugin agents that aren't installed at all now appear in the picker as "skills · CLI not found" and get skill files if picked. And after a run that keeps a plugin, the next aitools update writes the raw skills back next to it. There are a few nits, plus one question about picker preselection.

Questions

  1. In the interactive picker, a detected agent whose CLI isn't on PATH (for example Claude Code desktop) is labeled "skills · CLI not found" but stays unticked, because IsPreselected (libs/aitools/agents/detect.go:82) only pre-checks agents that can complete an install automatically. A non-interactive run of the same command now installs skill files for that agent. Is the difference intended? Or should these agents be preselected now that they get skill files?

Agreeing with existing threads

  • [nit] Telemetry per-agent results report a kept plugin as a CLI_NOT_ON_PATH failure: #7482 (comment)

Comment thread cmd/aitools/install.go Outdated
item.reason = reason
item.skipErrorCategory = protos.AitoolsErrorCategoryUnsupportedScope
} else {
case !explicit && !a.HasBinary(ctx):

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 new !explicit && !a.HasBinary(ctx) branch doesn't check that the agent is detected. The interactive picker calls planItemFor for every agent in agents.Registry. So on a machine without Codex or Copilot (no config dir, no CLI), those rows now print "skills · CLI not found" where they used to print "not found". If the user picks one, skill files are written for an agent that isn't installed. That creates its config dir, so every later non-interactive run treats the agent as detected and installs for it. Before this change, that pick failed with recovery steps. Please limit the fallback to detected agents, e.g. case !explicit && !a.HasBinary(ctx) && a.Detected(ctx):, and add a not-detected case to TestPlanItemForAgentWithoutCLI.

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.

Fixed in 09d55e8: the branch is now case !explicit && !a.HasBinary(ctx) && a.Detected(ctx):, so an agent with neither its config dir nor its CLI is labeled "not found" in the picker again and, if picked, gets the plugin attempt and its recovery steps as before. Added a "not installed" case (Copilot) to TestPlanItemForAgentWithoutCLI.

Comment thread cmd/aitools/install.go Outdated
}
// Raw skills an earlier run gave this agent would show every skill
// twice next to the plugin, so remove them as a plugin install does.
if err := cleanupLegacyFn(ctx, it.agent, opts.Scope); err != 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.

[nice to have] The kept path removes the agent's raw skills but writes no plugin record to the CLI state. aitools update writes raw skills to every agent from DetectInstalled that excludePluginAgents doesn't filter, and that filter only checks state.Plugins. So the next update that changes a skill (a new release, or --force) writes the raw skills back into ~/.claude/skills next to the plugin. That brings back the duplicate listing the kept path exists to prevent. Example: Claude Code desktop plus Cursor; the user installs the plugin in Claude, runs aitools install (kept, raw skills removed), then aitools update. Please record the kept plugin in the state, or have update skip agents whose own manifest shows the plugin, and extend the acceptance sequence through an 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.

Fixed in 09d55e8 on the update side: excludePluginAgents now also drops an agent whose own manifest shows the plugin for the scope, next to the existing check of the CLI's records. I did not record the kept plugin in the state: a record would make update and uninstall run the agent's CLI for it, which is the CLI that is missing. The acceptance sequence now runs aitools update --force after the kept install and checks that Claude Code still has no skill files (it fails without the fix). Unit test: TestExcludePluginAgentsReadsTheAgentsManifest.

Comment thread cmd/aitools/install.go Outdated
project = cwd
}
}
active, err := a.DatabricksPluginActiveIn(ctx, project)

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] DatabricksPluginActiveIn counts a plugin that is disabled in Claude's settings. In the kept path, cleanupLegacyFn then removes the raw skills from an earlier fallback. A user whose plugin is installed but disabled ends up with no active databricks skills, and the run reports success. Consider removing the raw skills only when the plugin is enabled, or mention in the kept notice that the plugin is disabled.

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.

Leaving this one as is. The CLI has no reader for Claude Code's enabled and disabled plugins: that lives in Claude's layered settings files (user, project and local), and reading them reliably is a change of its own. A disabled plugin is also a choice the user made in Claude Code, and the kept notice already says the plugin is there and how to refresh it. Running databricks aitools install --skills-only gives the skill files back if someone wants them while the plugin stays disabled.

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.

Correction to my earlier reply: --skills-only is not a lasting workaround for a disabled plugin. The next default install keeps the plugin and removes those files, and update now does the same. A disabled plugin stays a known limitation of the kept path, because the CLI does not read Claude Code's enabled and disabled settings.

# Claude Code records its plugins in installed_plugins.json. The run keeps the
# plugin and removes the skill files the previous run gave Claude Code.
"$mkdir_bin" -p "${USERPROFILE:-$HOME}/.claude/plugins"
printf '%s\n' '{"plugins":{"databricks@claude-plugins-official":[{"scope":"user","version":"0.2.9"}]}}' > "${USERPROFILE:-$HOME}/.claude/plugins/installed_plugins.json"

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 says to commit static test inputs instead of creating them in script at test time. The installed_plugins.json written here with printf is a fixed literal. Please check it in next to the test and cp it into ~/.claude/plugins/.

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 09d55e8: the manifest is checked in as installed_plugins.json next to the test, and the script copies it into ~/.claude/plugins/.

Comment thread libs/aitools/agents/plugins.go Outdated
// HasPluginManifest reports whether the CLI can read the agent's own plugin
// manifest (Claude Code). For such agents the manifest is authoritative: a
// plugin removed with the agent's own CLI leaves the CLI's state record behind.
func (a *Agent) HasPluginManifest() bool {

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] HasPluginManifest() returns true for Claude Code even when installed_plugins.json doesn't exist. It reports that the CLI has a manifest reader for the agent. The comment in pluginAlreadyInstalled ("When the agent has a readable plugin manifest") reads the same way. Consider renaming it to HasPluginManifestReader (or similar) so callers don't take it as a file-existence check.

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.

Renamed to HasPluginManifestReader in 09d55e8, with a doc comment saying it reports a reader, not that the file exists. The comment in pluginAlreadyInstalled now says "When the CLI can read the agent's own plugin manifest", and the scope rules moved into a small manifestPluginActive helper that update uses too.

Comment thread cmd/aitools/recovery.go
// cliMissingReason is why an agent whose CLI is not on PATH does not get the
// plugin.
func cliMissingReason(it agentPlanItem) string {
return fmt.Sprintf("'%s' is not on PATH", it.agent.Binary)

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] cliMissingReason builds '%s' is not on PATH, and pluginFailureText (recovery.go:395) builds the same string separately. Both strings appear in the acceptance output (the fallback notice and the named-agent failure). If one changes, they drift apart. Have pluginFailureText call cliMissingReason for the binary-missing case.

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 09d55e8: pluginFailureText uses cliMissingReason(it) for the binary-missing case, so the fallback notice and the named-agent failure share one string.

@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 5261133 to 09d55e8 Compare November 11, 2026 21:58
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 5c39d6a to 9da45c5 Compare November 12, 2026 08:41
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 8d5b0c0 to 7c43b03 Compare November 12, 2026 08:44
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 9da45c5 to 0595a19 Compare November 12, 2026 08:48
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 7c43b03 to e50e58a Compare November 12, 2026 08:48
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 0595a19 to 52e34eb Compare November 12, 2026 08:51
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from e50e58a to a5688b2 Compare November 12, 2026 08:55
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 52e34eb to 181bc21 Compare November 12, 2026 09:00
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from a5688b2 to 1c5b9d5 Compare November 12, 2026 09:00
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 181bc21 to e8d4f91 Compare November 12, 2026 09:04
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 1c5b9d5 to 357f7ff Compare November 12, 2026 09:04
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from e8d4f91 to 2eca9b0 Compare November 12, 2026 09:06
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 357f7ff to 122bf1b Compare November 12, 2026 09:06
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 2eca9b0 to cf6eeb8 Compare November 12, 2026 09:31
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 122bf1b to 461f2f4 Compare November 12, 2026 09:31
anton-107 and others added 12 commits November 12, 2026 09:55
`aitools update`, `list`, `version` and `uninstall` now log the same `aitools_command_event` funnel as `aitools install` (STARTED, then FINISHED with the outcome and error category) and resolve auth best-effort in the background so the events can upload at exit. `list` and `version` never wait for that resolution at exit, and neither do `update` and `uninstall` runs that end before their work (flag and scope errors, a declined confirm, nothing installed, a `--skills` name that is not installed), as for `install` before its plan is confirmed; their events upload only if auth has already resolved.

```diff
 NewUpdateCmd / NewListCmd / NewVersionCmd / NewUninstallCmd
-  return cmd
+  return instrumentCommand(cmd, protos.AitoolsCommand<X>)
+    cmd.RunE = withCommandFunnel(...)  // STARTED; FINISHED from RunE's error
+    withTelemetryAuth(cmd)             // background auth; no wait at exit unless
+                                       // RunE calls waitForTelemetryAuth (update and
+                                       // uninstall, once a scope has state)
```

```text
finish(err)
  nil                                  -> SUCCESS
  errors.Is(err, ErrNothingInstalled)  -> NOTHING_TO_DO  (still exits 1 with the same text)
  prompt aborted / no agents picked    -> CANCELLED
  otherwise                            -> FAILURE, classifyInstallError(err)
                                          (scope flag errors tagged INVALID_FLAGS, as in install)

RunE returns nil, but
  uninstall confirm answered "no"                      -> CANCELLED
  uninstall removed nothing and nothing failed         -> NOTHING_TO_DO
  every plugin/skill update or removal attempted       -> FAILURE with the first error's category
    failed and nothing else succeeded                     (still exits 0)
  (via funnelFrom(ctx).finishWith)
```

Installer additions: `ErrNothingInstalled` (same error text as before), `PluginUpdate.Err` (failed plugins are returned after the updated ones; `update` prints only the updated ones), `UpdateResult.CheckErr` (the manifest fetch error a `--check` run only warns about), and `UninstallSkillsWithResult` returning `UninstallResult{PluginsRemoved, SkillsRemoved, PluginFailures, SkillFailures}`, which replaces `UninstallSkills` and `UninstallSkillsOpts`. A plugin record for an agent this CLI does not know counts as a failed update or removal (`ErrUnknownAgent`, UNKNOWN_AGENT), and a pre-state (legacy) install returns `ErrLegacyInstall` (same text, STATE_INVALID). `uninstall --skills` with a skill that is not installed returns a `SkillError` (same text). Full uninstall now removes skills in sorted order.

Changelog: `.nextchanges/cli/aitools-telemetry-funnel.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
`databricks version --check` (text output) is followed by the `apps init` skills tip on stderr when a coding agent is detected without Databricks tooling. Stdout keeps only the update-check result:

```diff
 Databricks CLI v0.245.0
 You're on the latest version.
+Tip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.
```

The tip comes from `agents.MaybeVersionCheckTip`. It is not printed when an agent has the databricks plugin (in its own manifest or recorded in the CLI install state) or Databricks skills (in the shared store, in the agent's own skills dir, or in the project dirs up to the repository root), on Databricks Runtime, or when Claude Code drives the CLI (`agents.MaybeHint` already covers that caller). Changelog fragment: `.nextchanges/cli/version-check-aitools-hint.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
`databricks aitools install` runs `<agent> plugin --help` to check that the agent's CLI can install plugins. Agent CLIs are large Node/Rust binaries, and a cold start (first run, antivirus scan, slow container disk) can take several seconds. When the probe times out, the agent is reported as unavailable.

```diff
 // libs/aitools/installer/plugin.go
-pluginProbeTimeout = 5 * time.Second
+pluginProbeTimeout = 20 * time.Second
```

The same constant also bounds `<agent> plugin marketplace list`. Changelog fragment: `.nextchanges/cli/aitools-plugin-probe-timeout.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
… 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>
Plugin installs no longer fail when the lookup of the latest skills release on api.github.com fails (for example HTTP 403 when rate-limited). The agent's own CLI fetches the plugin from its marketplace, so the skills ref only labels the recorded plugin version. On lookup failure the CLI now records the plugin version as unknown and keeps the scope's existing `state.Release`.

```diff
 executePlan (cmd/aitools/install.go)
   if plugin items
     ref = GetSkillsRef(ctx)
-    lookup error -> install fails
+    lookup error -> debug log, ref = "" (plugin version unknown)
     install plugins

 RecordPluginInstalls (libs/aitools/installer/plugin.go)
-  state.Release = ref
+  if ref != "": state.Release = ref
```

`DATABRICKS_SKILLS_REF` and a pinned compat version still take precedence. Skills installs are unchanged.

Changelog fragment: `.nextchanges/cli/aitools-plugin-ref-fallback.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
`aitools install` failures that used to be `UNCATEGORIZED` now get a specific `error_category`. The installer returns typed errors and `classifyInstallError` maps them:

```text
classifyInstallError(err)
  BlockedError
    TimedOut                     -> PLUGIN_CLI_TIMEOUT   (agent CLI killed by its timeout)
  FetchError{Stage, StatusCode, RateLimited}   (release / manifest / skill file)
    context.Canceled             -> COMMAND_INTERRUPTED
    transport failure            -> NETWORK / TIMEOUT     (no response, or a timeout/reset while reading the body)
    RateLimited                  -> RATE_LIMITED          (429, or 403 with GitHub's rate-limit headers)
    clicompat.ErrNotFound        -> MANIFEST_NOT_FOUND
    Stage == release             -> RELEASE_LOOKUP_FAILED
    JSON syntax/type error       -> MANIFEST_INVALID
    otherwise                    -> SKILLS_FETCH_FAILED
  StateError{Path}               -> STATE_INVALID         (unparsable .state.json)
```

- `FetchError` and `StateError` keep the wrapped error's message, so what the user sees does not change.
- A fetch that fails on the transport (no response, or a timeout or reset while reading the body) keeps reporting `NETWORK` or `TIMEOUT`, as before.
- A missing manifest wraps `clicompat.ErrNotFound`, so `clicompat.IsNotFoundError` stays the one way to check for a missing ref.
- An unreadable state file is not a `StateError`: it keeps its filesystem category.
- `FetchManifest` reads the whole body (capped at 1 MiB, like the release lookup) before parsing it, so a connection closed cleanly mid-body (`SKILLS_FETCH_FAILED`; a reset reports `NETWORK`) is told apart from an empty or truncated manifest (`MANIFEST_INVALID`).
- Changelog fragment: `.nextchanges/cli/aitools-install-fetch-error-categories.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
…under its own reason

On Windows, the `codex.cmd` and `copilot.bat` shims can print the full `plugin --help` and then exit 1. Because of that, `databricks aitools install` reported a CLI that is present as `cli-not-on-path`, which points the user at the wrong fix. The probe now reports that case as `shim-exited-with-error` and still stops before running any plugin command:

```diff
 probePluginCLI(agent)
   bin = lookPath(agent CLI)
   err = run `<bin> plugin --help` (20s timeout)
-  if err: block cli-not-on-path
+  if err and windowsShimPrintedHelp(bin, err): block shim-exited-with-error
+      ("printed its plugin help but exited with an error (<exit>)[: <stderr>];
+        to see the error and exit code, run
+        `& "<bin>" plugin --help; $LASTEXITCODE` in PowerShell or
+        `"<bin>" plugin --help & echo %ERRORLEVEL%` in Command Prompt, and
+        re-run this command once it exits with 0")
+  if err: block cli-not-on-path

+windowsShimPrintedHelp(bin, err)
+  bin ends in .cmd or .bat (LookPath returns these only on Windows)
+  and err is not a timeout
+  and stdout has a `Usage: <bin> plugin` line
+      and indented `install`/`add` and `marketplace` command lines
```

`blockedErrorCategory` maps the new reason to `PLUGIN_INSTALL_FAILED`, the closest existing category.

Changelog fragment: `.nextchanges/cli/aitools-windows-shim-probe.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
Coding agents often run commands in a pseudo-terminal (Cursor, Copilot in the VS Code terminal). Stdin looks like a TTY, so `databricks aitools install` showed its scope select and agent picker, and nobody was there to answer them. The run blocked.

Now a caller that `useragent.AgentProvider()` identifies as an agent always gets the non-interactive defaults: global scope and every detected agent.

On a terminal, when a prompt would otherwise have run, it prints a note to stderr that names the detected agent by its `--agents` value (or says more than one was detected) and the missing `--scope`/`--agents` flags of `databricks aitools install`, for example `No prompts shown because a coding agent (cursor) was detected. Pass --scope and --agents to databricks aitools install to choose what to install.` An agent env var inherited from an agent session can reach a person's shell, and the note tells them why the picker is gone.

The change is scoped to install. These prompts still check only `cmdio.IsPromptSupported` and are left for a follow-up: the `Proceed?` confirm of `uninstall`, the both-scopes prompts of `update` and `uninstall`, and the `apps init` skills prompt.

```diff
 install RunE
+  interactive := cmdio.IsPromptSupported(ctx) && useragent.AgentProvider() == ""
-  resolveScopeWithPrompt(ctx)                         // prompted on any TTY
+  resolveScopeWithPrompt(ctx, interactive)
-  selectAgents(ctx, scope, skillsOnly)                // picker on any TTY
+  selectAgents(ctx, scope, skillsOnly, interactive)
-  if !explicit && cmdio.IsPromptSupported(ctx)        // plan summary + confirm
+  if !explicit && interactive
```

- The `install` help text now describes the non-interactive defaults.
- The install funnel records `interactive` from the same check, so an agent caller's run is reported as non-interactive.
- The interactive install tests clear the agent env with `isolateAgents`, so they keep prompting when the test binary runs under an agent (`CLAUDECODE`, `AI_AGENT`, ...).
- Changelog fragment: `.nextchanges/cli/aitools-install-no-prompts-for-agents.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
`databricks aitools install` (interactive, no agents on the command line) no longer asks `Proceed?` after the agent picker. Agents that would be skipped are not offered in the picker, so choosing agents already confirms the plan. The install scope moves into the picker title, so it is visible before anything runs, and the plan summary after the picker shrinks to the scope line.

```diff
 install (interactive picker path)
-  promptAgentSelection     # "Select agents to set up", "enter to confirm"
+  promptAgentSelection     # "Select agents to set up (<scope> scope)", "enter to install"
   buildPlan
-  printPlanSummary         # "Plan (<scope> scope):" + one line per agent
-  promptProceed            # "Proceed?" — No => "Cancelled.", exit 0
-    if !proceed: funnel CANCELLED, return nil
   funnel PLAN_CONFIRMED
+  printInstallScope        # "Install scope: <scope>"
   install plugins / skills
```

- `promptProceed` and `defaultPromptProceed` are removed. `promptAgentSelection` takes the scope for its title.
- `printPlanSummary` becomes `printInstallScope`. Its per-agent lines repeated the picker, and its skip branch could no longer run.
- The picker hint reads "space to toggle, enter to install", since Enter now starts the install.
- Under `--skills-only` (and the `databricks skills install` alias), every found agent is labeled "skills" in the picker, skipped rows included, because that is what they get. The removed per-agent summary lines were the only place this showed before. Rows are also pre-checked by config dir in that mode, so a plugin agent whose CLI is not on PATH starts checked, matching the non-interactive run.
- `PLAN_CONFIRMED` is kept and still follows `AGENTS_SELECTED` right away; its comment in `libs/telemetry/protos` now says the picker selection is the confirmation.
- `TestInstallFunnelInteractiveCancel` now cancels at the picker, which is the last prompt before the install.
- Changelog fragment: `.nextchanges/cli/aitools-install-drop-proceed-confirm.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
`databricks aitools install` no longer stops when installing skill files fails. Agents that get a plugin (Claude Code) don't depend on the skill files, so their plugin is still installed. The skills error is still the command's error. The plugin is recorded in the install state without advancing `state.Release`, because the skill files on disk are still at the old release; otherwise `aitools version` and a pinned `aitools update` would report them as up to date. If skills from an install that predates state tracking are on disk with no state file, the plugin is not recorded at all, so `aitools update` and the install's legacy checks still see those skills.

```diff
 executePlan
   install skills for skills agents
-    on error: return error            # plugin agents never attempted
+    on error: remember skillsErr
   install plugins for plugin agents
-    on state write error: return err
+    on state write error: return errors.Join(skillsErr, err)
+    record plugins; if skillsErr, keep state.Release   # skills on disk are still at the old release
+    if skillsErr and untracked legacy skills on disk: don't create a plugin-only state file
+  if skillsErr: return skillsErr      # stays the top-level error
   if per-agent errors: return agentErrors
```

Changelog fragment: `.nextchanges/cli/aitools-install-skills-failure-keeps-plugins.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
A failed `databricks aitools install` now ends with a step the user (or an agent calling the CLI) can take, in the text output and in a new `recovery` field of `--output json` (top level and per agent).

```text
install RunE
  rerun := the run's flags (scope, agents, skills, --path, --skills-only, --experimental, --output json)
  ... install ...
  on error: withRecovery(ctx, rerun, err)          # cmd/aitools/recovery.go
    per-agent errors               -> no extra step (each agent printed its own)
    empty picker selection         -> name --scope and --agents to skip prompts
    switch classifyInstallError(err)                # the telemetry category
      USER_ABORTED, PROMPT_FAILED  -> name --scope and --agents to skip prompts
      NETWORK, TIMEOUT (fetch with no response), RELEASE_LOOKUP_FAILED, RATE_LIMITED,
      MANIFEST_NOT_FOUND, MANIFEST_INVALID, SKILLS_FETCH_FAILED -> per host / HTTP status / missing release
      STATE_INVALID                -> move the state file aside, re-run (global scope: without --skills)
      SKILL_NOT_FOUND, VERSION_INCOMPATIBLE -> 'databricks aitools list', or upgrade the CLI
      EXPERIMENTAL_SKILL           -> no extra step (the message names --experimental)
      PERMISSION_DENIED            -> check ownership (e.g. an earlier sudo run)
      FILESYSTEM                   -> check the path is writable and has free space
      otherwise                    -> RefusedError (downgrade, legacy guard): no extra step;
                                      else re-run with --debug, report at github.com/databricks/cli/issues
  per-agent plugin failure / skip -> exact agent CLI commands
                                     (installer.ManualPluginInstallCommands) or the run's
                                     command narrowed to that agent
```

- The recovery step is chosen from the category `classifyInstallError` returns, so the advice and the telemetry category cannot disagree.
- Every re-run command, including the per-agent ones, repeats this run's choices (scope, `--skills-only`, `--skills`, `--experimental`, `--output json`), so a copied command never widens to every agent or the global scope. Agents chosen by flag or in the picker are named; auto-detected agents are not (the retry detects them again). A skipped agent gets the command for just that agent in the global scope.
- `--path`, `--agents` and `--skills` values are quoted for the user's shell: POSIX quoting, or the new `shellquote.WindowsArg` for cmd.exe/PowerShell. If no quoting keeps a value literal (`$`, `` ` ``, `%`, `!`, `"`, control characters on Windows; control characters on POSIX), the step says "your original install command" instead of printing a command that could expand or run something. On Windows, `--path` is printed with forward slashes, so it pastes the same into cmd.exe, PowerShell and Git Bash. A command that contains a single quote is delimited with backticks instead of single quotes.
- The installer's downgrade refusal and legacy-install guard return the new `installer.RefusedError`; their messages already name the fix, so no generic step is added when the refusal is the failure. The legacy-install message now names `--scope=global --skills-only`, the install that rebuilds skills state now that plugins are the default.
- The step appended to an error drops its final period (error strings must not end with punctuation). The error still unwraps to the original, so the telemetry category does not change.
- "No supported coding agents found" now also says how to name an agent that was not detected.
- Changelog fragment: `.nextchanges/cli/aitools-install-recovery.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
`databricks aitools install` used to exit 0 when it set up nothing, so an agent calling it believed the install worked. `update` and `uninstall` did the same when every attempt failed. All three now exit with code 1 in that case.

```diff
 install
-  no agent detected                     -> message, exit 0
+  no agent detected                     -> message, errNoAgentSetUp (NOTHING_TO_DO)
   executePlan
     if an agent named via --agents failed
       return its error
+    if no agent was installed
+      return errNoAgentSetUp  # "nothing was installed; follow the steps above"
-  no skill in the release to install    -> "Installed 0 skills.", exit 0
+  no skill in the release to install    -> installer.ErrNoSkillsResolved + step (NOTHING_TO_DO)
 update / uninstall
-  every attempt failed                  -> warnings, exit 0
+  every attempt failed                  -> "nothing was updated|checked: <cause>" / "could not remove any skill or plugin: <cause>"
```

- The FINISHED event keeps the categories it recorded before: the first failed or skipped agent's, or the first cause's for update and uninstall. A run with no agent detected, and one with no skill to install (also with `--path`), finish NOTHING_TO_DO while still exiting 1. When a plugin was installed, a release with no skill to install only skips the skills agents.
- `nothingInstalledCategory` is gone: the returned error now carries the outcome. `anyInstalled` replaces it.
- A plugin record for an agent this CLI does not know is not counted as a failed attempt: no command can act on it, so update and uninstall keep exiting 0 for it.
- Uninstall decides from `UninstallResult.SkillsChanged`, which replaces `SkillsRemoved`: the skills of which the run removed a copy an agent reads (a symlink, an entry, or the canonical dir). With `--agents`, removing only the canonical dir does not count when a targeted agent keeps its entry. "Uninstalled N skills." no longer counts skills whose removal failed.
- `update --check` no longer prints "No changes." when the manifest could not be fetched.
- The legacy `skills install` alias silences cobra's own error and usage output, so the new error prints once.
- The help text of install, update and uninstall says when they exit 1.
- Changelog fragment: `.nextchanges/notable-changes/aitools-install-nothing-installed-fails.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from cf6eeb8 to 3289cf7 Compare November 12, 2026 10:05
A detected coding agent whose CLI is not on PATH (for example a Claude Code desktop or IDE install: `~/.claude` exists, `claude` does not) used to be skipped, so the run left it with nothing. It now gets raw skill files, unless it already has the plugin.

The plan decides this, so the picker label and what the run does agree, and the agent joins the one skills batch of the run:

```diff
 planItemFor (plugin agent, scope supported)
-  plugin
+  if CLI not on PATH, the agent is detected (config dir), and not named via --agents
+    if the agent already has the plugin for this scope
+      keep it (plugin delivery, nothing installed; outcome "kept")
+    else
+      skill files instead (skills delivery)
+  else
+    plugin
```

- "Already has the plugin": Claude Code's own `installed_plugins.json` decides (a `claude plugin uninstall` leaves the CLI's record behind); when that file exists but cannot be read, the CLI's install state for the scope decides, as it does for other agents. A global install counts only a machine-wide plugin. A project-scope install counts only a project-scope plugin of this project: its skill files are shared through the repository, while a user or local plugin is personal. For Codex and GitHub Copilot only a plugin installed by `databricks aitools` is known, so one installed another way shows every skill twice next to the skill files.
- A kept plugin: install prints how to refresh it and, before the skills batch, removes raw skills an earlier run gave the agent, as a plugin install does. The picker labels it "plugin installed · CLI not found".
- Skill files instead: install prints how to switch to the plugin, and after the skills install drops a stale plugin record from the CLI's install state (failure to do so is a warning).
- A global-scope fallback in a directory where the agent has a project or local databricks plugin still runs, and warns that this project will see both the plugin and the global skill files.
- A kept plugin counts like an installed one: the run succeeds, with telemetry outcome `NOTHING_TO_DO` when it did nothing else.
- Agents named via `--agents`, and agents whose CLI is on PATH but whose `plugin` command fails, keep failing with the exact commands to run.
- `aitools update` no longer tries to migrate an agent whose CLI is not on PATH from raw skills to the plugin, and no longer writes raw skills to an agent whose own manifest shows the plugin for the scope (a kept plugin has no CLI record): it removes them instead, and skips the reconcile when no agent is left that can take file skills in the scope.
- New: `installer.PluginRecorded` (also used for the User-Agent tooling), `installer.ForgetPluginInstalls`, `agents.Agent.HasPluginManifestReader`, and an exported `agents.Agent.DatabricksPluginActiveIn` that reports an unreadable manifest as an error.
- New acceptance test `acceptance/experimental/aitools/skills/install-non-interactive`.
- Changelog fragment: `.nextchanges/cli/aitools-install-skills-fallback.md`.

Co-authored-by: Isaac <no-reply@databricks.com>
@anton-107
anton-107 force-pushed the aitools/skills-fallback-for-detected-agents branch from 461f2f4 to eae0662 Compare November 12, 2026 10:05
@anton-107
anton-107 force-pushed the aitools/nothing-installed-fails branch from 3289cf7 to c6b1b5e Compare November 12, 2026 11:33

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