Skip to content

[CFX-5789] [CFX-5790] ToolRegistry & Surface environment-aware failure messaging#577

Open
taras-pokornyy wants to merge 6 commits into
datarobot-oss:mainfrom
taras-pokornyy:CFX-5789_TOOL_RGISTRY
Open

[CFX-5789] [CFX-5790] ToolRegistry & Surface environment-aware failure messaging#577
taras-pokornyy wants to merge 6 commits into
datarobot-oss:mainfrom
taras-pokornyy:CFX-5789_TOOL_RGISTRY

Conversation

@taras-pokornyy

@taras-pokornyy taras-pokornyy commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

RATIONALE

When dr deps install runs a tool's install command and it fails, the previous error was a bare exit-code line with no guidance on what to try next. This change wires up the existing ToolRegistry data to produce actionable, environment-aware tips when an install command fails — "You have pyenv — try: pip install uv" — and falls back to the canonical install command (e.g. curl | sh) when no manager is detected. Permission-denied failures (exit code 126 or matching stderr text) get an OS-specific "re-run with sudo / as Administrator" message instead.

CHANGES

PR Automation

Comment-Commands: Trigger CI by commenting on the PR:

  • /trigger-smoke-test or /trigger-test-smoke - Run smoke tests
  • /trigger-install-test or /trigger-test-install - Run installation tests

Labels: Apply labels to trigger workflows:

  • run-smoke-tests or go - Run smoke tests on demand (only works for non-forked PRs)

Important

For Forked PRs: The run-smoke-tests label won't work. A required Smoke Tests check will block merge until a maintainer acts:

  • A maintainer uses /approve-smoke-tests to run smoke tests (results will set the check)
  • A maintainer uses /skip-smoke-tests to bypass the check without running tests

Please comment requesting a maintainer review if you need smoke tests to run.


Note

Low Risk
UX and messaging only around failed installs; success path unchanged. Suggestions are advisory shell commands, not auto-executed.

Overview
When dr deps install fails, users now get structured output on the writer (not only a minimal error): what was tried, optional tips, retry command, and docs URL. InstallPrerequisites tees install output into a buffer so failures can be classified and messaged.

A new ToolRegistry maps tools (python, uv, node, pulumi, task, git) to ordered install strategies (pyenv/brew/asdf/winget/etc.) plus fallbacks. DetectEnvironment probes PATH and NVM layout; selectInstallStrategy picks the first matching manager, skipping the manager that just failed. buildInstallFailureMsg / buildInstallTip add OS-specific guidance for permission errors (exit 126 / stderr heuristics) vs alternate install commands (e.g. “You have pyenv — try: pip install uv”). Returned errors are shortened; detail lives in stdout.

Broad unit coverage was added for registry, strategy selection, and failure messaging.

Reviewed by Cursor Bugbot for commit 6c10ba7. Configure here.

@taras-pokornyy taras-pokornyy self-assigned this Jun 16, 2026

fmt.Fprint(w, msg)

return installed, fmt.Errorf("install failed for %q (exit code %d)", prerequisite.Name, exitCode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Start flow hides install failures

High Severity

Detailed install failure text is written only to the io.Writer, while the returned error is a short exit-code message. The quickstart TUI installs deps into a buffer and on failure displays only m.err, so users lose tips, the tried command, and streamed command output that this change added.

Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules for DataRobot CLI

Reviewed by Cursor Bugbot for commit 29b422a. Configure here.

Comment thread internal/dependencies/installer.go
Comment thread internal/dependencies/installer.go
@taras-pokornyy

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6c10ba7. Configure here.

Comment thread internal/dependencies/installer.go
// Unix-like systems (Linux & macOS) return 126 when a file lacks execute permissions
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && exitCode == 126 {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Exit 126 ignored on Windows

Medium Severity

Permission-denied handling treats exit code 126 only when runtime.GOOS is linux or darwin. Installs still run via sh -c on Windows (e.g. Git Bash), where 126 can mean “not executable,” but without matching stderr text the failure is not classified as permission denied and users miss the Administrator-oriented tip.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules for DataRobot CLI

Reviewed by Cursor Bugbot for commit 6c10ba7. Configure here.

@taras-pokornyy taras-pokornyy requested a review from a team June 16, 2026 17:32
Comment thread internal/dependencies/registry.go
func (fs FallbackStrategy) getStrategyTip(goos string) string {
cmds := fs.Commands

if goos == "windows" && len(fs.CommandsWindows) > 0 {

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 here:

Do we have reusable values in this repo. so we're not using magic strings for things like "windows" and likely "darwin" and "linux" ?

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.

"windows", "darwin", and "linux" are all potential values of runtime.GOOS, which is a build-time constant.

https://pkg.go.dev/internal/goos#GOOS
https://cs.opensource.google/go/go/+/refs/tags/go1.26.4:src/internal/goos/zgoos_linux.go;l=7

I am not recommending this because I'm not convinced it's worth it here but one way to approach this would be to conditionally compile the fallbackstrategy code using go build. (You can extend packages based on runtime.GOOS and runtime.GOARCH.) A working example of this can be found in internal/workload where Wojciech has implemented OS-specific diskspace implementations.

What MIGHT be worth doing is something like this, where we enumerate the OS we actually support.

     type OS string

     const (
         OSDarwin  OS = "darwin"
         OSLinux   OS = "linux"
         OSWindows OS = "windows"
     )

     func IsWindows(os OS) bool { return os == OSWindows }
     func IsUnixLike(os OS) bool { return os == OSDarwin || os == OSLinux }

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.

This is all just informational though, since I know @taras-pokornyy you want a bigger discussion on how we can scale this registry.

return ""

case 1:
return " Try: " + cmds[0]

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.

So wasn't sure where to put this.

Curious what you and @ajalon1 and others think.

I like your usage of \t elsewhere and wonder if that would be better to use than the many places you're using double spaces??

Suggested change
return " Try: " + cmds[0]
return "\tTry: " + cmds[0]

To me it's:

  • less likely to accidentally have one or three spaces at some point as a UI bug
  • easier to read in the code as opposed to empty spaces - maybe that's just me though

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.

I think it'd be good to stick to a single convention, but that becomes a renderer / UI issue and I think we can do this better with a more structured formatting approach than just \t. text/tabwriter is a helpful package if we want to go that route.

@c-h-russell-walker c-h-russell-walker left a comment

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.

Looks great - made some nit comments. And thanks for the demo today during our meeting.

@ajalon1 ajalon1 left a comment

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.

This LGTM as is. The registry could use some more work, but that is out of scope.

The one thing I would suggest addressing is what happens with strategy.WithVersion(""). You can either do that here or in a followup.

nvmDir := getenv("NVM_DIR")

if nvmDir == "" {
home := getenv("HOME")

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.

The correct way to do this would be home, err := os.UserHomeDir()

func (fs FallbackStrategy) getStrategyTip(goos string) string {
cmds := fs.Commands

if goos == "windows" && len(fs.CommandsWindows) > 0 {

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.

"windows", "darwin", and "linux" are all potential values of runtime.GOOS, which is a build-time constant.

https://pkg.go.dev/internal/goos#GOOS
https://cs.opensource.google/go/go/+/refs/tags/go1.26.4:src/internal/goos/zgoos_linux.go;l=7

I am not recommending this because I'm not convinced it's worth it here but one way to approach this would be to conditionally compile the fallbackstrategy code using go build. (You can extend packages based on runtime.GOOS and runtime.GOARCH.) A working example of this can be found in internal/workload where Wojciech has implemented OS-specific diskspace implementations.

What MIGHT be worth doing is something like this, where we enumerate the OS we actually support.

     type OS string

     const (
         OSDarwin  OS = "darwin"
         OSLinux   OS = "linux"
         OSWindows OS = "windows"
     )

     func IsWindows(os OS) bool { return os == OSWindows }
     func IsUnixLike(os OS) bool { return os == OSDarwin || os == OSLinux }

return ""
}

return strategy.withVersion(prerequisite.MinimumVersion).getStrategyTip(goos)

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.

What happens if prerequisite.MinimumVersion == ""? Will we get a string like "nvm install {version}" back?

func (fs FallbackStrategy) getStrategyTip(goos string) string {
cmds := fs.Commands

if goos == "windows" && len(fs.CommandsWindows) > 0 {

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.

This is all just informational though, since I know @taras-pokornyy you want a bigger discussion on how we can scale this registry.

}

// isPermissionDenied inspects exit codes and stderr text across OS types.
func isPermissionDenied(exitCode int, stderr string) bool {

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.

Comment thread internal/dependencies/installer.go
return ""

case 1:
return " Try: " + cmds[0]

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.

I think it'd be good to stick to a single convention, but that becomes a renderer / UI issue and I think we can do this better with a more structured formatting approach than just \t. text/tabwriter is a helpful package if we want to go that route.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants