Skip to content

aitools: print a recovery step for every install failure - #7480

Open
anton-107 wants to merge 1 commit into
aitools/skills-failure-keeps-pluginsfrom
aitools/install-recovery-steps
Open

anton-107 wants to merge 1 commit into
aitools/skills-failure-keeps-pluginsfrom
aitools/install-recovery-steps

Conversation

@anton-107

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

Copy link
Copy Markdown
Contributor

Stacked on #7479.

Stacked on: aitools: keep installing plugins when the skills install fails

Summary

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).

The recovery field is a human-readable next-step string, not a stable machine contract: its wording can change, so callers should show it or pass it on, not parse it.

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.

Evidence

  • Before: with the --path branch's return withRecovery(...) temporarily changed back to return err (not committed):
    install --path "my skills" against an unreachable skills host
    --- FAIL: TestInstallPathFailureHasRecoveryForThePathRun
      "failed to fetch manifest: Get \"http://127.0.0.1:1/v0.2.6/manifest.json\": ... connection refused"
      does not contain "\nCould not download Databricks skills from 127.0.0.1:1."
      does not contain "then re-run `databricks aitools install --path='my skills'`"
    
    After: at the current head:
    --- PASS: TestInstallPathFailureHasRecoveryForThePathRun
    --- PASS: TestInstallPickerAbortSuggestsFlags
    --- PASS: TestInstallFailureAfterPickerNamesTheSelectedAgents
    --- PASS: TestPluginFailureTextKeepsTheRunsFlags
    --- PASS: TestSkipRecoveryNamesOnlyTheAgentInTheGlobalScope
    --- PASS: TestPromptRecoveryBuildsOnTheChoicesMade
    
  • go test ./cmd/aitools/... ./libs/aitools/... ./libs/shellquote/... and ./task test-exp-aitools (with the cmd/aitools and experimental/aitools acceptance tests) pass on Linux. The acceptance goldens show the new lines, for example:
    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'
    
    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
    

Merge Danger

Door: two-way

Output only: no new network calls, waits, or state writes, and exit codes are unchanged.

Blast Radius: small

  • Error text on stderr gets an extra line, and a per-agent skip or failure gets an indented step under it. Scripts that compare install error strings exactly will see the change. --output json gains an optional recovery field, which is additive and meant for display, not parsing.
  • Windows quoting (shellquote.WindowsArg) is unit-tested with goos passed explicitly, but these tests have not run on Windows or macOS. GOOS=windows/darwin go vet passes.

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 force-pushed the aitools/skills-failure-keeps-plugins branch from 891a0e3 to e15f2f9 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 force-pushed the aitools/install-recovery-steps branch from 23225b1 to e0ad83a Compare November 11, 2026 14:34
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 2b5410a93 | Workflow run

Regression test report

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

15 tests: regression=11 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-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:37165" ("[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-metadata3741392416/002/output.txt

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:44315" ("[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-metadata4030499543/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:39139" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:39139" ("[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-tip2587860804/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:44011" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:44011" ("[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-tip3713736471/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:43793" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43793" ("[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/TestAcceptcmdaitoolstelemetryhint877262159/002/output.txt

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:37775" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:37775" ("[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/TestAcceptcmdaitoolstelemetryhint2397723161/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:45017" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:45017" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall1935013938/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\":763,\"exit_code\":0},\"aitools_install_event\":{\"agents\":[\"CLAUDE_CODE\"],\"scope\":\"GLOBAL\",\"error_category\":\"TYPE_UNSPECIFIED\"}}}}"
        +    ]
        +  }
        +}
         
         >>> gron.py [TEST_TMP_DIR]/home/.databricks/token-cache.json
        -json.tokens.test-profile.access_token = "expired-access-token";
        +json.tokens.test-profile.access_token = "oauth-token";
        
FAIL

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40891" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40891" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall2633280246/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\"}}}}"
        +    ]
        +  }
        +}
         
         >>> gron.py [TEST_TMP_DIR]/home/.databricks/token-cache.json
        -json.tokens.test-profile.access_token = "expired-access-token";
        +json.tokens.test-profile.access_token = "oauth-token";
TestAccept/cmd/aitools/telemetry/install-failure: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:34411" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:34411" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure3761496659/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,29 +24,15 @@
         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"}]}}

latest (v1.26.0):

acceptance_test.go:1213: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:39939" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:39939" ("[DATABRICKS_HOST]")
    acceptance_test.go:1345: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure47672165/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,29 +24,15 @@
         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"}]}}
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:43427" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43427" ("[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-uninstall508229221/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:35775" ("[DATABRICKS_HOST]")
    acceptance_test.go:1213: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:35775" ("[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-uninstall1574374310/002/output.txt
    acceptance_test.go:1123: 
        LOG.install: Installing Databricks skills for Cursor...
        LOG.install: Using skills version test-ref
        LOG.install: Fetching skills manifest...
        LOG.install: Installed 1 skill.
    acceptance_test.go:1123: LOG.install-telemetry: {"command":"aitools_install","exit_code":0,"aitools_install_event":{"agents":["CURSOR"],"scope":"GLOBAL","error_category":"TYPE_UNSPECIFIED"}}
TestAccept/cmd/aitools/telemetry/workspace-unreachable: branch ✅ | main (8b4f052) ❌ | latest (v1.26.0) ❌

main (8b4f052):

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

latest (v1.26.0):

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

4 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: 696613f

Run: 43197007857

Env 💚​RECOVERED ✅​pass 🙈​skip Time
💚​ aws linux-2core-8gb 1 270 30 10:19
💚​ aws-windows-latest-4core-16gb 1 272 28 10:00
💚​ azure linux-2core-8gb 1 269 30 8:15
💚​ azure-windows-latest-4core-16gb 1 271 28 9:02
💚​ gcp linux-2core-8gb 1 270 30 11:03
💚​ gcp-windows-latest-4core-16gb 1 272 28 14:05
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
3:58 azure-windows-latest-4core-16gb TestAccept
3:54 azure linux-2core-8gb TestAccept
3:52 aws-windows-latest-4core-16gb TestAccept
3:46 gcp linux-2core-8gb TestAccept
3:42 gcp-windows-latest-4core-16gb TestAccept
3:14 aws linux-2core-8gb TestAccept
2:27 gcp linux-2core-8gb TestFsCpDirToDirFileNotOverwritten/dbfs_to_dbfs
2:06 gcp linux-2core-8gb TestFsCpDir/dbfs_to_dbfs
2:05 aws-windows-latest-4core-16gb TestFsCpFileToDirFileNotOverwritten/dbfs_to_uc-volumes

@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from e15f2f9 to 0fb3e5c Compare November 11, 2026 16:53
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from e0ad83a to 06eddb3 Compare November 11, 2026 16:53
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 0fb3e5c to 520f504 Compare November 11, 2026 16:55
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 06eddb3 to 0254738 Compare November 11, 2026 16:55
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 520f504 to 6709c89 Compare November 11, 2026 16:57
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 0254738 to 4c846ec Compare November 11, 2026 16:57

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

Not approving yet: 2 open design questions.

This PR adds a recovery step to every databricks aitools install failure, in the error text and in a new recovery field of --output json, and the re-run commands repeat the run's flags with shell-aware quoting. The approach helps users, the tests pass, and CI is green. I found nothing blocking, but several steps are wrong or misleading for specific inputs. Examples: guard errors that already say what to do get a contradictory "report a bug" line, a dropped download is reported as "returned HTTP 200", the multi-agent --agents value is not quoted for PowerShell, and the skip, prompt, and fallback steps can widen the run or drop --experimental. There are also two design questions: whether recovery text should come from its own error switch, and whether the re-run flags should travel through the context.

Questions

  1. The recovery text comes from a second switch over the installer error types (installRecovery/fetchRecovery), which mirrors classifyInstallError/fetchErrorCategory in a different order. The two can already disagree: a rename that fails with EACCES is PERMISSION_DENIED in telemetry but gets the generic "report a bug" step. Do you want to keep a separate switch, key the recovery text off the category classifyInstallError returns, or write the advice into the errors where they are created, as the experimental-skill and legacy-guard errors already do?
  2. The re-run flags travel through context.WithValue, and ctx is reassigned three times in RunE, although every reader (withRecovery, buildInstallOutput, pluginFailureText via executePlan) is reached from RunE. Is the context value deliberate? Passing installInvocation as a parameter would make the dependency visible, and a missed assignment could not silently fall back to the bare command.

Comments outside the changed lines

  • [nit] .github/PULL_REQUEST_TEMPLATE.md: .agents/skills/pr-checklist/SKILL.md asks PR bodies to follow .github/PULL_REQUEST_TEMPLATE.md (## Changes, ## Why, ## Tests, in that order), and recent merged PRs do. This body uses ## Summary, ## Evidence, and ## Merge Danger. Moving the content under the template headings keeps it consistent with the rest of the repo.

Comment thread cmd/aitools/recovery.go Outdated
if perr, ok := errors.AsType[*fs.PathError](err); ok && errors.Is(err, fs.ErrPermission) {
return fmt.Sprintf("Check that you own %s and can write to it (an earlier run with sudo can leave it owned by root), then re-run %s.", perr.Path, rerunCommand(ctx))
}
return "Re-run " + rerunCommand(ctx, "--debug") + " to see details. If it keeps failing, report it at " + cliIssuesURL + "."

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] Untyped installer errors that already include their own fix fall through to the catch-all, which tells the user to re-run the same command with --debug and report a bug. Example: with legacy skills on disk and no state file, databricks aitools install --skills-only --agents=cursor --skills=databricks-sql fails with "legacy install detected ...; run 'databricks aitools install' (without a skill name) first". It then adds "Re-run 'databricks aitools install --scope=global --agents=cursor --skills-only --skills=databricks-sql --debug' to see details. If it keeps failing, report it at https://github.com/databricks/cli/issues". That re-run fails the same way every time. The downgrade refusal ("set DATABRICKS_SKILLS_REF=...") gets the same line. Give these errors a type, or tag them where they are created, and return "" for them in installRecovery, as the experimental-skill case already does.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2781109: the downgrade refusal and the legacy-install guard now return a new installer.RefusedError (message unchanged, still UNCATEGORIZED in telemetry), and installRecovery returns "" for it, so neither gets the --debug / report line.

Comment thread cmd/aitools/recovery.go Outdated
return host + " returned HTTP " + strconv.Itoa(ferr.StatusCode) + ", a server-side error. Wait a few minutes and re-run " + rerun + "."
case ferr.StatusCode == http.StatusForbidden || ferr.StatusCode == http.StatusProxyAuthRequired:
return host + " refused the download (HTTP " + strconv.Itoa(ferr.StatusCode) + "). A proxy or access policy may block it; ask your network admin to allow " + host + ", then re-run " + rerun + "."
case ferr.StatusCode != 0:

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] FetchManifest and fetchSkillFile keep StatusCode: 200 when io.ReadAll fails, so a connection that drops mid-body lands in the StatusCode != 0 branch. The user sees "raw.githubusercontent.com returned HTTP 200. Re-run 'databricks aitools install --debug' to see the URL, and report it at https://github.com/databricks/cli/issues" for what is a network failure. Send statuses below 400 to the no-response step (default), or require StatusCode >= 400 in this branch. Add a table case with StatusCode: 200 and a read error.

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 2781109: downloadRecovery sends any status below 400 to the no-response step ("Could not download Databricks skills from . Check that this machine can reach it ..."). Added the StatusCode: 200 + read-error table case.

Comment thread cmd/aitools/recovery.go Outdated

// skipRecovery returns the recovery step for an agent the plan skipped.
func skipRecovery(it agentPlanItem) string {
return "Re-run with --scope=global to set up " + it.agent.DisplayName + "."

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] "Re-run with --scope=global to set up Cursor." asks the user to edit their own command, which can widen the run or fail. With --scope=project --agents=claude-code,cursor, Claude Code is installed in the project and Cursor is skipped; re-running with --scope=global also installs Claude Code's plugin at user scope. With the deprecated --project, adding --scope=global fails with "cannot use --scope with --project or --global". Print a complete command for just this agent instead: installCommand with the scope set to global, plus --skills-only when the run used it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2781109: the skip step is now a complete command for just that agent in the global scope, built from the run's flags, for example To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only. It always spells --scope, so a run that used the deprecated --project gets a command that works too.

Comment thread cmd/aitools/recovery.go Outdated
args = append(args, "--scope="+inv.scope)
}
if inv.agents != "" {
args = append(args, "--agents="+inv.agents)

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] --agents is appended without quoting, but WindowsArg in this PR treats , as special, because PowerShell reads it as the array operator. On Windows, after a picker selection of two agents, the step prints databricks aitools install --scope=global --agents=claude-code,cursor --skills-only --skills="a,b": --skills is quoted and --agents is not. Pasted into PowerShell, the agents arrive as two arguments (--agents=claude-code and cursor), and cobra.NoArgs rejects the command. Pass the joined list through quoteArg(goos, ...) like --skills, and add a two-agent Windows case to TestRenderInstallCommandQuotesUserInput.

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 2781109: --agents now goes through quoteArg(goos, ...) like --skills. Added a two-agent Windows case (--agents="claude-code,cursor" --skills-only --skills="a,b"), plus the POSIX one.

Comment thread cmd/aitools/recovery.go Outdated
// installCommand is the install command for one agent in the plan's scope. The
// scope is always spelled out: without it a non-interactive run defaults to
// global, which is not what a project-scoped run asked for.
func installCommand(it agentPlanItem, extra ...string) string {

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] installCommand is a second builder for databricks aitools install lines, and it drops flags the run used. After databricks aitools install --agents=claude-code --experimental hits a plugin failure, the fallback prints databricks aitools install --agents=claude-code --scope=global --skills-only, which installs without the experimental skills. The two builders also put the flags in different orders (--agents ... --scope here, --scope ... --agents in renderInstallCommand). Build the per-agent commands from rerunInvocation(ctx) with agents set to this agent, through renderInstallCommand, so there is one builder.

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 2781109: there is one builder now. A per-agent command is renderInstallCommand of the run's invocation with agents set to that agent, so --experimental, --skills and --output json carry over, and the flag order is the same everywhere (--scope ... --agents ...). Test: TestPluginFailureTextKeepsTheRunsFlags.

Comment thread cmd/aitools/recovery.go Outdated
// Without --skills the command holds no user-typed value, so it is always
// printed as a command.
inv.skills = ""
return step + rerunPhrase(runtime.GOOS, inv) + " to reinstall all skills and rebuild the state file. An install of named skills needs the state file."

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 state file also holds plugin records: RecordPluginInstalls writes to the same .state.json. After moving it aside and re-running a --skills-only command for Cursor, only Cursor's skill records come back. A Claude Code plugin recorded in the old file is no longer tracked, and aitools uninstall leaves it installed. "rebuild the state file" overstates this. Say that other agents set up in this scope need their install re-run too, for example databricks aitools install --scope=<scope>.

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 2781109: "rebuild the state file" is gone. When the re-run is limited to some agents or to --skills-only, the step adds "The state file also tracked the other agents set up in this scope; re-run their install too, for example 'databricks aitools install --scope=global'".

Comment thread cmd/aitools/install.go Outdated
if e := topLevelFailure(runErr); e != nil {
out.Error = e.Error()
out.ErrorCategory = string(classifyInstallError(e))
out.Recovery = installRecovery(ctx, e)

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] In --output json mode, the top-level and per-agent recovery commands omit --output json. An agent that runs the suggested command gets text output instead of the JSON it parses. Record JSON mode in installInvocation and append --output json when rendering.

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 2781109: installInvocation records JSON mode, and every rendered command, top level and per agent, appends --output json (see the updated install-output-json-* goldens).

Comment thread cmd/aitools/recovery.go Outdated
return skillRecovery(ctx, skerr)
}
if perr, ok := errors.AsType[*fs.PathError](err); ok && errors.Is(err, fs.ErrPermission) {
return fmt.Sprintf("Check that you own %s and can write to it (an earlier run with sudo can leave it owned by root), then re-run %s.", perr.Path, rerunCommand(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.

[nit] .agents/rules/style-guide-go.md asks for file paths in output to use forward slashes (filepath.ToSlash), so output is the same on every OS. The ownership step (line 185, perr.Path) and the state-file step (line 196, serr.Path) print the OS path. Wrap both in filepath.ToSlash.

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 2781109: both paths go through filepath.ToSlash. The tests build their paths with filepath.FromSlash, so they check this on Windows too.

Comment thread cmd/aitools/recovery.go Outdated
if err == nil {
return nil
}
if _, ok := errors.AsType[*recoveryError](err); ok {

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.md asks not to add checks for cases the callers rule out. Three checks here never fire. withRecovery checks whether the error already has a step, but none of its four call sites in install.go passes a wrapped error. installRecovery checks err == nil, which both callers already rule out. ManualPluginInstallCommands returns nil for agents without a plugin or binary (plugin.go:471), which cannot happen on the ReasonInstallFailed path; if it ever did, the step would print "Run '' to see the full error". Remove them, or add a comment saying why they are needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Partly fixed in 2781109. Removed the "already has a step" check in withRecovery and the err == nil check in installRecovery. withRecovery keeps its nil check, because RunE also calls withRecovery(ctx, rerun, runErr) on success, when runErr is nil; the doc comment says so now. There is no check around ManualPluginInstallCommands to remove: InstallPluginForAgent returns early for an agent without a plugin, and a ReasonInstallFailed error only comes after the agent's CLI ran, so the list is never empty on that path.

Comment thread cmd/aitools/recovery_test.go Outdated
}

func TestFetchRecoveryNamesOverriddenHost(t *testing.T) {
t.Setenv("DATABRICKS_SKILLS_BASE_URL", "https://mirror.example.com/skills")

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.md asks for fixture hosts under an RFC 2606 §2 TLD, and the repo convention is .test. mirror.example.com resolves. The URL is only parsed here, but mirror.example.test follows the rule at no cost.

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 2781109: mirror.example.test (and ghe.example.test in the new release-host test).

@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch 2 times, most recently from e7d2703 to 912057b Compare November 11, 2026 19:42
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 4c846ec to 2781109 Compare November 11, 2026 19:51
@anton-107

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Every inline comment is addressed in 2781109 (replies in each thread). On the two questions and the note outside the diff:

Question 1. I keyed the recovery text off the category. installRecovery is now a single switch classifyInstallError(err), so the advice and the telemetry category cannot disagree; the EACCES rename now gets the ownership step. A case still reads the error for details the category does not carry: host and stage from the FetchError, the path from the StateError, PathError or LinkError. Three checks run before the switch, for errors that already say what to do or have no category of their own: per-agent errors, the new installer.RefusedError (downgrade refusal and legacy guard, still UNCATEGORIZED in telemetry), and the empty picker selection (which the funnel reports as CANCELLED). I didn't move the advice into the errors where they are created, because most steps depend on this run's flags (the re-run command) and the user's shell, and the installer knows neither.

Question 2. It wasn't deliberate, and it's a parameter now. RunE builds one installInvocation and passes it to withRecovery, buildInstallOutput and executePlan, which hands it to pluginFailureText and skipRecovery. The context key and its getter are gone, so a missed update fails to compile instead of falling back to the bare command.

PR template. I kept the current layout. This PR is part of a stack whose descriptions all use the same Summary / Evidence / Merge Danger sections, and keeping them alike makes the stack easier to read side by side. The content maps onto the template: Summary is Changes and Why, Evidence is Tests.

@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 912057b to 5f9958b Compare November 11, 2026 20:03
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 2781109 to 255a5ca Compare November 11, 2026 20:03
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 5f9958b to 6f53d25 Compare November 11, 2026 20:07
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 255a5ca to 2db10d1 Compare November 11, 2026 20:07
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 6f53d25 to bced029 Compare November 11, 2026 21:04
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 3d99ac0 to 3cf1d79 Compare November 11, 2026 22:27
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from e3135af to 5d98a15 Compare November 11, 2026 22:32
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 3cf1d79 to 3d59637 Compare November 11, 2026 22:36
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 5d98a15 to 12aee78 Compare November 11, 2026 22:39
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 3d59637 to 7e52920 Compare November 11, 2026 22:39
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 12aee78 to 08b09de Compare November 11, 2026 22:59
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 7e52920 to ad1c106 Compare November 11, 2026 22:59
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 08b09de to 4f38e11 Compare November 12, 2026 00:40
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from ad1c106 to 4844908 Compare November 12, 2026 00:46

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

Not approving yet: part of the automated review did not complete; 1 open design question.

This PR gives every failed databricks aitools install a next step in the text output and in a new recovery field of --output json. The re-run commands repeat the run's flags and are quoted for the user's shell. Every thread from the earlier rounds is addressed at this head, CI is green on Linux, macOS and Windows, and I found nothing blocking; only a small doc-comment nit remains. One question about the PR's scope is open: whether the top-level recovery steps and the JSON field should land separately from the per-agent steps.

Questions

  1. You explained earlier why the steps rebuild the full command. A related question is scope. This PR adds per-agent steps for skipped and failed agents, where users had no next step before. It also adds a step for every top-level failure, the shellquote.WindowsArg helper, and a new recovery field in --output json, which agent callers may start to depend on. Would you land the per-agent steps first and move the top-level steps and the JSON field to a follow-up, or keep them together? If they stay together, could the description say whether recovery is meant to be a stable field?

Comment thread libs/shellquote/quote.go Outdated
// PowerShell pass to the program as one argument, without expanding anything
// in it. It returns the argument unquoted unless it contains whitespace (any
// Unicode space: PowerShell splits arguments at a no-break space too), a
// cmd.exe command separator, or 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.

[nit] The WindowsArg doc comment breaks early after "a cmd.exe command separator, or a" on line 46, so line 47 reads like a new sentence fragment. Reflow lines 45-47 to the same width as the rest of the comment.

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 1f0c9c4: reflowed the whole WindowsArg paragraph to the comment's usual width; no wording changes.

@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 4844908 to 1f0c9c4 Compare November 12, 2026 08:23
@anton-107

Copy link
Copy Markdown
Contributor Author

Thanks. The doc-comment nit is fixed in 1f0c9c4.

Question 1. I'd keep them together. The per-agent steps and the top-level steps share the same pieces: the run's invocation, the command builder and shellquote.WindowsArg. Without the top-level steps the per-agent ones would still need all of that, and a top-level failure (a failed download, a rate-limited release lookup, a corrupt state file) is where users most often had no next step. The JSON field is the same text the error prints, so splitting it out would give --output json callers less than text-mode users get.

On stability: I added a sentence to the description. recovery is a human-readable next-step string, not a stable machine contract. Its wording can change, so callers should show it to a person or pass it on, not parse it. Callers that branch on the outcome should use status and error_category.

@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 4f38e11 to d0d5fc2 Compare November 12, 2026 08:36
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch 2 times, most recently from a37d96c to e745931 Compare November 12, 2026 08:41
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from d0d5fc2 to 55882e3 Compare November 12, 2026 08:48
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from e745931 to b501f02 Compare November 12, 2026 08:48
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 55882e3 to 942cec5 Compare November 12, 2026 08:51
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from b501f02 to bac174e Compare November 12, 2026 08:51
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 942cec5 to ddd4225 Compare November 12, 2026 09:00
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from bac174e to 8772447 Compare November 12, 2026 09:00
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from ddd4225 to 45b26f8 Compare November 12, 2026 09:06
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 8772447 to 5e5073d Compare November 12, 2026 09:06
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 45b26f8 to a3e42ce Compare November 12, 2026 09:30
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 5e5073d to cd0f02f Compare November 12, 2026 09:31
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from a3e42ce to cad677d Compare November 12, 2026 10:01
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from cd0f02f to 696613f Compare November 12, 2026 10:02
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from cad677d to 2adef03 Compare November 12, 2026 11:02
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>
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 696613f to 2b5410a Compare November 12, 2026 11:02

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