Skip to content

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

Merged
anton-107 merged 1 commit into
mainfrom
aitools/install-recovery-steps
Nov 13, 2026
Merged

anton-107 merged 1 commit into
mainfrom
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: c9931c104 | Workflow run

Regression test report

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

4 tests: regression=4 unreleased=0 coverage=0 failing=0
test branch main (eee3c42) latest (v1.26.0)
TestAccept/cmd/aitools/telemetry/install-failure ✅ ❌ ❌
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/telemetry/install-failure: branch ✅ | main (eee3c42) ❌ | latest (v1.26.0) ❌

main (eee3c42):

acceptance_test.go:1242: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:36713" ("[DATABRICKS_HOST]")
    acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:36713" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure1864997172/002/output.txt
        @@ -6,7 +6,6 @@
         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"}}
        @@ -24,7 +23,6 @@
         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"}}
        @@ -42,7 +40,6 @@
         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"}}
        @@ -57,7 +54,6 @@
         
         >>> [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
        
FAIL

latest (v1.26.0):

acceptance_test.go:1242: Substituted DATABRICKS_CLI_GITHUB_API_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:41675" ("[DATABRICKS_HOST]")
    acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:41675" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: Diff:
        --- cmd/aitools/telemetry/install-failure/output.txt
        +++ /tmp/TestAcceptcmdaitoolstelemetryinstall-failure2659477033/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"}]}}
        
FAIL
TestAccept/experimental/aitools/skills/install-output-json-agent-error: branch ✅ | main (eee3c42) ❌ | latest (v1.26.0) ❌

main (eee3c42):

acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:37857" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== install --output json records a per-agent error category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=claude-code,cursor --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"claude-code\",\n      \"delivery\": \"skills\",\n      \"status\": \"installed\"\n    },\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\",\n      \"recovery\": \"To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	actual  : "\n=== install --output json records a per-agent error category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=claude-code,cursor --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"claude-code\",\n      \"delivery\": \"skills\",\n      \"status\": \"installed\"\n    },\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -17,4 +17,3 @@
        	            	       "error_category": "UNSUPPORTED_SCOPE",
        	            	-      "message": "does not support project-scoped skills",
        	            	-      "recovery": "To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json"
        	            	+      "message": "does not support project-scoped skills"
        	            	     }
        	Test:       	TestAccept/experimental/aitools/skills/install-output-json-agent-error
        	Messages:   	experimental/aitools/skills/install-output-json-agent-error/output.txt vs /tmp/TestAcceptexperimentalaitoolsskillsinstall-output-json-agent2930933483/002/output.txt

latest (v1.26.0):

acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:42619" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== install --output json records a per-agent error category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=claude-code,cursor --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"claude-code\",\n      \"delivery\": \"skills\",\n      \"status\": \"installed\"\n    },\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\",\n      \"recovery\": \"To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	actual  : "\n=== install --output json records a per-agent error category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=claude-code,cursor --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"claude-code\",\n      \"delivery\": \"skills\",\n      \"status\": \"installed\"\n    },\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -17,4 +17,3 @@
        	            	       "error_category": "UNSUPPORTED_SCOPE",
        	            	-      "message": "does not support project-scoped skills",
        	            	-      "recovery": "To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json"
        	            	+      "message": "does not support project-scoped skills"
        	            	     }
        	Test:       	TestAccept/experimental/aitools/skills/install-output-json-agent-error
        	Messages:   	experimental/aitools/skills/install-output-json-agent-error/output.txt vs /tmp/TestAcceptexperimentalaitoolsskillsinstall-output-json-agent2058985345/002/output.txt
TestAccept/experimental/aitools/skills/install-output-json-agents-skipped: branch ✅ | main (eee3c42) ❌ | latest (v1.26.0) ❌

main (eee3c42):

acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:41639" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== install --output json: every named agent skipped, each with its own category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=cursor,codex --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\",\n      \"recovery\": \"To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json\"\n    },\n    {\n      \"name\": \"codex\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\",\n      \"recovery\": \"To set up Codex CLI in the global scope, run: databricks aitools install --scope=global --agents=codex --skills-only --output json\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	actual  : "\n=== install --output json: every named agent skipped, each with its own category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=cursor,codex --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\"\n    },\n    {\n      \"name\": \"codex\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -12,4 +12,3 @@
        	            	       "error_category": "UNSUPPORTED_SCOPE",
        	            	-      "message": "does not support project-scoped skills",
        	            	-      "recovery": "To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json"
        	            	+      "message": "does not support project-scoped skills"
        	            	     },
        	            	@@ -20,4 +19,3 @@
        	            	       "error_category": "UNSUPPORTED_SCOPE",
        	            	-      "message": "does not support project-scoped skills",
        	            	-      "recovery": "To set up Codex CLI in the global scope, run: databricks aitools install --scope=global --agents=codex --skills-only --output json"
        	            	+      "message": "does not support project-scoped skills"
        	            	     }
        	Test:       	TestAccept/experimental/aitools/skills/install-output-json-agents-skipped
        	Messages:   	experimental/aitools/skills/install-output-json-agents-skipped/output.txt vs /tmp/TestAcceptexperimentalaitoolsskillsinstall-output-json-agent3624461404/002/output.txt

latest (v1.26.0):

acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:45557" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== install --output json: every named agent skipped, each with its own category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=cursor,codex --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\",\n      \"recovery\": \"To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json\"\n    },\n    {\n      \"name\": \"codex\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\",\n      \"recovery\": \"To set up Codex CLI in the global scope, run: databricks aitools install --scope=global --agents=codex --skills-only --output json\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	actual  : "\n=== install --output json: every named agent skipped, each with its own category\n\n>>> [CLI] aitools install --skills-only --scope=project --agents=cursor,codex --output json\n{\n  \"scope\": \"project\",\n  \"agents\": [\n    {\n      \"name\": \"cursor\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\"\n    },\n    {\n      \"name\": \"codex\",\n      \"delivery\": \"skip\",\n      \"status\": \"skipped\",\n      \"error_category\": \"UNSUPPORTED_SCOPE\",\n      \"message\": \"does not support project-scoped skills\"\n    }\n  ]\n}\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -12,4 +12,3 @@
        	            	       "error_category": "UNSUPPORTED_SCOPE",
        	            	-      "message": "does not support project-scoped skills",
        	            	-      "recovery": "To set up Cursor in the global scope, run: databricks aitools install --scope=global --agents=cursor --skills-only --output json"
        	            	+      "message": "does not support project-scoped skills"
        	            	     },
        	            	@@ -20,4 +19,3 @@
        	            	       "error_category": "UNSUPPORTED_SCOPE",
        	            	-      "message": "does not support project-scoped skills",
        	            	-      "recovery": "To set up Codex CLI in the global scope, run: databricks aitools install --scope=global --agents=codex --skills-only --output json"
        	            	+      "message": "does not support project-scoped skills"
        	            	     }
        	Test:       	TestAccept/experimental/aitools/skills/install-output-json-agents-skipped
        	Messages:   	experimental/aitools/skills/install-output-json-agents-skipped/output.txt vs /tmp/TestAcceptexperimentalaitoolsskillsinstall-output-json-agent484016174/002/output.txt
TestAccept/experimental/aitools/skills/install-output-json-error: branch ✅ | main (eee3c42) ❌ | latest (v1.26.0) ❌

main (eee3c42):

acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:43851" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== install --output json reports a top-level error category and exits non-zero\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code --skills=nonexistent --output json\n{\n  \"scope\": \"global\",\n  \"agents\": [],\n  \"error\": \"skill \\\"nonexistent\\\" not found\",\n  \"error_category\": \"SKILL_NOT_FOUND\",\n  \"recovery\": \"Run 'databricks aitools list' to see the available skills.\"\n}\n\nExit code: 1\n"
        	            	actual  : "\n=== install --output json reports a top-level error category and exits non-zero\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code --skills=nonexistent --output json\n{\n  \"scope\": \"global\",\n  \"agents\": [],\n  \"error\": \"skill \\\"nonexistent\\\" not found\",\n  \"error_category\": \"SKILL_NOT_FOUND\"\n}\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -8,4 +8,3 @@
        	            	   "error": "skill \"nonexistent\" not found",
        	            	-  "error_category": "SKILL_NOT_FOUND",
        	            	-  "recovery": "Run 'databricks aitools list' to see the available skills."
        	            	+  "error_category": "SKILL_NOT_FOUND"
        	            	 }
        	Test:       	TestAccept/experimental/aitools/skills/install-output-json-error
        	Messages:   	experimental/aitools/skills/install-output-json-error/output.txt vs /tmp/TestAcceptexperimentalaitoolsskillsinstall-output-json-error2365658174/002/output.txt

latest (v1.26.0):

acceptance_test.go:1242: Substituted DATABRICKS_SKILLS_BASE_URL "$DATABRICKS_HOST" -> "http://127.0.0.1:40675" ("[DATABRICKS_HOST]")
    acceptance_test.go:1374: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1374
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1115
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n=== install --output json reports a top-level error category and exits non-zero\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code --skills=nonexistent --output json\n{\n  \"scope\": \"global\",\n  \"agents\": [],\n  \"error\": \"skill \\\"nonexistent\\\" not found\",\n  \"error_category\": \"SKILL_NOT_FOUND\",\n  \"recovery\": \"Run 'databricks aitools list' to see the available skills.\"\n}\n\nExit code: 1\n"
        	            	actual  : "\n=== install --output json reports a top-level error category and exits non-zero\n\n>>> [CLI] aitools install --skills-only --scope=global --agents=claude-code --skills=nonexistent --output json\n{\n  \"scope\": \"global\",\n  \"agents\": [],\n  \"error\": \"skill \\\"nonexistent\\\" not found\",\n  \"error_category\": \"SKILL_NOT_FOUND\"\n}\n\nExit code: 1\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -8,4 +8,3 @@
        	            	   "error": "skill \"nonexistent\" not found",
        	            	-  "error_category": "SKILL_NOT_FOUND",
        	            	-  "recovery": "Run 'databricks aitools list' to see the available skills."
        	            	+  "error_category": "SKILL_NOT_FOUND"
        	            	 }
        	Test:       	TestAccept/experimental/aitools/skills/install-output-json-error
        	Messages:   	experimental/aitools/skills/install-output-json-error/output.txt vs /tmp/TestAcceptexperimentalaitoolsskillsinstall-output-json-error4140952973/002/output.txt

@eng-dev-ecosystem-bot

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

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: c9931c1

Run: 43376242689

Env 💚​RECOVERED ✅​pass 🙈​skip Time
💚​ aws linux-2core-8gb 1 270 19 4:50
💚​ aws-windows-latest-4core-16gb 1 272 17 5:24
💚​ azure linux-2core-8gb 1 269 19 5:04
💚​ azure-windows-latest-4core-16gb 1 271 17 3:37
💚​ gcp linux-2core-8gb 1 270 19 5:37
💚​ gcp-windows-latest-4core-16gb 1 272 17 5:14
Test Name aws linux-2core-8gb aws-windows-latest-4core-16gb azure linux-2core-8gb azure-windows-latest-4core-16gb gcp linux-2core-8gb gcp-windows-latest-4core-16gb
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 6 slowest tests (at least 2 minutes):
duration env testname
5:17 aws-windows-latest-4core-16gb TestAccept
5:07 gcp-windows-latest-4core-16gb TestAccept
4:02 azure linux-2core-8gb TestAccept
3:56 gcp linux-2core-8gb TestAccept
3:31 aws linux-2core-8gb TestAccept
3:26 azure-windows-latest-4core-16gb TestAccept

@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/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
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 696613f to 2b5410a Compare November 12, 2026 11:02

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Approval from simonfaltum's review agent at 2b5410a, on Simon's decision.

Your answer to the design question in the earlier automated reviews is accepted, so it no longer blocks. Part of the last automated review did not complete; this approval rests on the parts that did, plus a check of the changes since (4844908 → 2b5410a), which address its comments.

Comment thread cmd/aitools/recovery.go Outdated
if a.HasBinary(ctx) {
reason = fmt.Sprintf("'%s plugin' is not available (%s)", a.Binary, blocked.Detail)
}
return reason, fmt.Sprintf("Install or update the %s CLI so '%s' is on PATH, then re-run %s. Or install skill files instead: %s", a.DisplayName, a.Binary, agentRerun(inv, it), skillsOnlyCommand(inv, it))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] When the binary is on PATH (a.HasBinary(ctx)), the reason says "'claude plugin' is not available", for example for an old CLI without the plugin subcommand. But the step still says "Install or update the Claude Code CLI so 'claude' is on PATH", and the CLI is already on PATH. In that case, tell the user to update the CLI to a version that has plugin.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8997feb: when the binary is on PATH, the step now says "Update the Claude Code CLI to a version with the 'claude plugin' command, then re-run '...'". The not-on-PATH case keeps its wording. TestPluginFailureTextCLIWithoutPluginCommand now checks the step too.

Comment thread cmd/aitools/recovery.go Outdated
}
return reason, fmt.Sprintf("Install or update the %s CLI so '%s' is on PATH, then re-run %s. Or install skill files instead: %s", a.DisplayName, a.Binary, agentRerun(inv, it), skillsOnlyCommand(inv, it))
case blocked.Reason == installer.ReasonShimExitedWithError:
return fmt.Sprintf("'%s plugin --help' exited with an error", a.Binary),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nice to have] For a shim that exited with an error, the text output prints only this reason and step (install.go:663-664). So it drops the stderr and the exit-code commands that #7476 puts in BlockedError.Detail; only the JSON message keeps them. #7476 says that a re-run can show only the help, so "Run 'codex plugin --help' to see the error" may show nothing new. Keep blocked.Detail in the reason, as the TimedOut and ReasonInstallFailed cases do.

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 8997feb: the reason now keeps blocked.Detail, as the TimedOut and ReasonInstallFailed cases do: "'codex plugin --help' printed its plugin help but exited with an error (...): ; to see the error and exit code, run ...". The step no longer repeats "run ' plugin --help'". It now reads "Once it exits with 0, re-run '...'. Or install skill files instead: ...". Test case updated.

Comment thread cmd/aitools/recovery.go
return permissionRecovery(inv, err)
case protos.AitoolsErrorCategoryFilesystem:
return filesystemRecovery(inv, err)
default:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] A Ctrl-C during a fetch is classified as COMMAND_INTERRUPTED, and that category has no case here. So it falls through to genericRecovery, which tells the user who cancelled to re-run with --debug and to report a bug. Return no step for COMMAND_INTERRUPTED, and add a test case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8997feb: COMMAND_INTERRUPTED now returns no step, so a Ctrl-C gets neither --debug nor a bug report. Added a table case for a fetch canceled mid-request.

@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 2adef03 to b9488bb Compare November 12, 2026 12:28
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 2b5410a to c8c2ccf Compare November 12, 2026 12:28
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from b9488bb to 3e7a5e5 Compare November 12, 2026 12:30
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from c8c2ccf to 1519a26 Compare November 12, 2026 12:30
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 3e7a5e5 to 916c212 Compare November 12, 2026 12:31
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 1519a26 to ec2d35f Compare November 12, 2026 12:31
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 916c212 to b6d9a96 Compare November 12, 2026 12:32
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch 2 times, most recently from 2ac7f12 to 8997feb Compare November 12, 2026 12:32
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from b6d9a96 to 838c303 Compare November 12, 2026 12:34
@anton-107
anton-107 force-pushed the aitools/install-recovery-steps branch from 8997feb to ef4ea8f Compare November 12, 2026 12:34
@anton-107
anton-107 force-pushed the aitools/skills-failure-keeps-plugins branch from 838c303 to 1eca02f Compare November 12, 2026 12:36
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>
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

❌ Integration test report

Commit: 532408b

Run: 43380019448

Env ❌​FAIL 🟨​KNOWN 💚​RECOVERED ✅​pass 🙈​skip Time
❌​ aws linux-2core-8gb 41 1 11 1510 1289 210:31
❌​ aws-windows-latest-4core-16gb 39 3 9 1431 1316 236:43
❌​ azure linux-2core-8gb 42 1 11 1489 1297 207:25
❌​ azure-windows-latest-4core-16gb 39 1 11 1411 1324 246:32
❌​ gcp linux-2core-8gb 39 1 11 1499 1295 224:26
❌​ gcp-windows-latest-4core-16gb 39 1 11 1418 1322 260:27
56 interesting tests: 44 FAIL, 9 RECOVERED, 3 KNOWN
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 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
❌​ TestAccept/bundle/config-remote-sync/external_cluster_policy_cross_target ❌​F 🙈​s ✅​p 🙈​s ✅​p 🙈​s
❌​ TestAccept/bundle/config-remote-sync/external_cluster_policy_cross_target/DMS= ❌​F ✅​p ✅​p
❌​ TestAccept/bundle/config-remote-sync/multiple_resources ✅​p 🙈​s ❌​F 🙈​s ✅​p 🙈​s
❌​ TestAccept/bundle/config-remote-sync/multiple_resources/DMS= ✅​p ❌​F ✅​p
❌​ TestAccept/bundle/config-remote-sync/multiple_resources/DMS=true ✅​p ❌​F ✅​p
💚​ TestAccept/bundle/resources/apps/lifecycle-started 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/apps/lifecycle-started-omitted 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/apps/lifecycle-started-omitted/DMS=true 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/apps/lifecycle-started-toggle 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/apps/lifecycle-started-toggle/DMS= 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/apps/lifecycle-started-toggle/DMS=true 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/apps/lifecycle-started/DMS= 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/apps/lifecycle-started/DMS=true 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
❌​ TestAccept/bundle/resources/postgres_branches/update_protected ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_branches/update_protected/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_branches/update_protected/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_branches/update_ttl ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_branches/update_ttl/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_branches/update_ttl/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_databases/update ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_databases/update/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_databases/update/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/remove_suspend_timeout ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/remove_suspend_timeout/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/remove_suspend_timeout/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_autoscaling ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_autoscaling/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_autoscaling/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_pg_settings ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_pg_settings/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_pg_settings/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_suspend_timeout ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_suspend_timeout/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_endpoints/update_suspend_timeout/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/add_default_endpoint_settings ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/add_default_endpoint_settings/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/add_default_endpoint_settings/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/remove_history_retention ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/remove_history_retention/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/remove_history_retention/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_default_endpoint_suspend ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_default_endpoint_suspend/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_default_endpoint_suspend/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_display_name ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_display_name/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_display_name/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_history_retention ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_history_retention/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_projects/update_history_retention/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_roles/update ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_roles/update/DMS= ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/postgres_roles/update/DMS=true ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
🟨​ TestAccept/bundle/resources/postgres_synced_tables/recreate 💚​R 🟨​K 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/postgres_synced_tables/recreate/DMS= 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🟨​ TestAccept/bundle/resources/postgres_synced_tables/recreate/DMS=true 💚​R 🟨​K 💚​R 💚​R 💚​R 💚​R
Top 50 slowest tests (at least 2 minutes):
duration env testname
13:03 azure-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=
11:23 azure-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=true
10:36 aws linux-2core-8gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=
10:28 aws-windows-latest-4core-16gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=
10:19 gcp linux-2core-8gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=true
10:15 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=true
9:45 aws-windows-latest-4core-16gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=true
9:34 azure linux-2core-8gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=true
9:29 aws linux-2core-8gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=true
9:27 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started-toggle/DMS=
9:17 gcp linux-2core-8gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=
9:16 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=
9:02 azure linux-2core-8gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=
9:01 azure-windows-latest-4core-16gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=
9:00 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=true
8:44 azure-windows-latest-4core-16gb TestAccept/bundle/resources/apps/lifecycle-started/DMS=true
8:40 aws linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=
8:05 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=
8:00 gcp linux-2core-8gb TestAccept/bundle/resources/clusters/deploy/local_ssd_count/DMS=
7:50 aws linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=true
7:40 aws linux-2core-8gb TestAccept/bundle/invariant/destroy_idempotent/DMS=/INPUT_CONFIG=cluster.yml.tmpl/READPLAN=1
7:27 gcp linux-2core-8gb TestAccept/bundle/config-remote-sync/multiple_resources/DMS=
7:27 aws-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=
7:24 gcp linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=
7:18 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/deploy/local_ssd_count/DMS=
7:13 aws linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started-toggle/DMS=
7:12 gcp linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=true
7:03 gcp linux-2core-8gb TestAccept/bundle/resources/clusters/deploy/local_ssd_count/DMS=true
6:57 gcp linux-2core-8gb TestAccept/bundle/resources/clusters/deploy/data_security_mode/DMS=
6:56 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/deploy/local_ssd_count/DMS=true
6:46 aws linux-2core-8gb TestAccept/bundle/config-remote-sync/multiple_resources/DMS=true
6:38 gcp linux-2core-8gb TestAccept/bundle/config-remote-sync/multiple_resources/DMS=true
6:36 aws linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started-toggle/DMS=true
6:14 aws linux-2core-8gb TestAccept/bundle/config-remote-sync/multiple_resources/DMS=
6:02 azure-windows-latest-4core-16gb TestAccept/pipelines/run/refresh-flags
6:00 aws linux-2core-8gb TestAccept/pipelines/run/refresh-flags
6:00 aws-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started-toggle/DMS=
5:58 aws linux-2core-8gb TestAccept/bundle/invariant/destroy_idempotent/DMS=/INPUT_CONFIG=cluster.yml.tmpl/READPLAN=
5:52 aws-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=true
5:37 aws-windows-latest-4core-16gb TestAccept/bundle/deploy/spark-jar-task/DMS=
5:36 aws linux-2core-8gb TestAccept/bundle/deploy/spark-jar-task/DMS=
5:28 azure linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started/DMS=
5:25 gcp linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started-toggle/DMS=true
5:20 aws-windows-latest-4core-16gb TestAccept/bundle/deploy/spark-jar-task/DMS=true
5:15 gcp linux-2core-8gb TestAccept/bundle/resources/clusters/lifecycle-started-toggle/DMS=
5:05 aws linux-2core-8gb TestAccept/bundle/deploy/spark-jar-task/DMS=true
5:00 gcp-windows-latest-4core-16gb TestAccept/bundle/deploy/spark-jar-task/DMS=
4:59 gcp-windows-latest-4core-16gb TestAccept/bundle/resources/clusters/lifecycle-started-toggle/DMS=true
4:59 aws linux-2core-8gb TestAccept/bundle/resources/apps/lifecycle-started-toggle/DMS=
4:57 azure-windows-latest-4core-16gb TestAccept/bundle/deploy/spark-jar-task/DMS=

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.

4 participants