Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/ucode/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,9 @@ def ensure_bootstrap_dependencies(
*,
update_existing: bool = False,
prompt_optional_updates: bool = True,
skip_cli_version_check: bool = False,
) -> None:
install_databricks_cli()
install_databricks_cli(skip_version_check=skip_cli_version_check)
install_tool_binary(
tool,
strict=True,
Expand Down
20 changes: 13 additions & 7 deletions src/ucode/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,11 @@ def _launch_tool(
needs_auto_configure = not existing.get("workspace") or tool not in (
existing.get("available_tools") or []
)
ensure_bootstrap_dependencies(tool, update_existing=needs_auto_configure)
ensure_bootstrap_dependencies(
tool,
update_existing=needs_auto_configure,
skip_cli_version_check=skip_preflight,
)
if needs_auto_configure:
_auto_configure_tool(tool)
state = ensure_provider_state(tool)
Expand Down Expand Up @@ -1574,15 +1578,17 @@ def _launch_tool(

# Launch-only escape hatch for managed/headless launchers (e.g. omnigent) that
# have already run `ucode configure`: skip the ~5-10s per-launch auth + AI
# Gateway re-validation. Distinct from the configure-only `--skip-validate`,
# which skips the model smoke test.
# Gateway re-validation, plus the Databricks CLI minimum-version check (whose
# `databricks aitools` floor otherwise false-positives on a usable public-preview
# build). Distinct from the configure-only `--skip-validate`, which skips the
# model smoke test.
SkipPreflightOption = Annotated[
bool,
typer.Option(
"--skip-preflight",
help="Skip the per-launch Databricks auth + AI Gateway re-validation, trusting a "
"prior `ucode configure`. Launches with your own local settings, ignoring any "
"workspace managed config.",
help="Skip the per-launch Databricks auth + AI Gateway re-validation (and the "
"Databricks CLI minimum-version check), trusting a prior `ucode configure`. "
"Launches with your own local settings, ignoring any workspace managed config.",
),
]

Expand Down Expand Up @@ -1656,7 +1662,7 @@ def _launch_managed_default(
return
if workspace:
set_current_workspace(normalize_workspace_url(workspace))
install_databricks_cli()
install_databricks_cli(skip_version_check=skip_preflight)
state = load_state()
current = state.get("workspace")
if not current:
Expand Down
15 changes: 12 additions & 3 deletions src/ucode/databricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,9 +649,17 @@ def ensure_databricks_cli_version() -> None:
ensure_databricks_cli_version()


def install_databricks_cli() -> None:
def install_databricks_cli(*, skip_version_check: bool = False) -> None:
"""Ensure the Databricks CLI is installed and (unless skipped) new enough.

``skip_version_check`` is set on ``--skip-preflight`` launches: they trust a
prior ``ucode configure`` and must not re-run the minimum-version gate, whose
``databricks aitools`` floor (v1.0.0) rejects a perfectly usable public-preview
build (e.g. v0.299.2) as a false positive. A missing CLI is still installed —
only the version *check* is bypassed."""
if shutil.which("databricks"):
ensure_databricks_cli_version()
if not skip_version_check:
ensure_databricks_cli_version()
return

print_section("Bootstrap")
Expand All @@ -662,7 +670,8 @@ def install_databricks_cli() -> None:
raise RuntimeError(
"Databricks CLI install completed, but `databricks` is still not on PATH."
)
ensure_databricks_cli_version()
if not skip_version_check:
ensure_databricks_cli_version()


def install_ai_tools(agent_tokens: list[str], profile: str | None = None) -> None:
Expand Down
35 changes: 32 additions & 3 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,9 @@ def test_triggers_when_no_workspace(self):
):
result = runner.invoke(app, ["claude"])
assert result.exit_code == 0, result.output
mock_bootstrap.assert_called_once_with("claude", update_existing=True)
mock_bootstrap.assert_called_once_with(
"claude", update_existing=True, skip_cli_version_check=False
)
mock_auto.assert_called_once_with("claude")

def test_triggers_when_tool_not_in_available_tools(self):
Expand All @@ -764,7 +766,9 @@ def test_triggers_when_tool_not_in_available_tools(self):
):
result = runner.invoke(app, ["claude"])
assert result.exit_code == 0, result.output
mock_bootstrap.assert_called_once_with("claude", update_existing=True)
mock_bootstrap.assert_called_once_with(
"claude", update_existing=True, skip_cli_version_check=False
)
mock_auto.assert_called_once_with("claude")

def test_skipped_when_already_configured(self):
Expand All @@ -787,9 +791,34 @@ def test_skipped_when_already_configured(self):
patch("ucode.cli.launch_agent"),
):
runner.invoke(app, ["claude"])
mock_bootstrap.assert_called_once_with("claude", update_existing=False)
mock_bootstrap.assert_called_once_with(
"claude", update_existing=False, skip_cli_version_check=False
)
mock_auto.assert_not_called()

def test_skip_preflight_bypasses_cli_version_check(self):
"""`--skip-preflight` tells bootstrap to skip the CLI minimum-version gate,
so a public-preview `databricks` (e.g. v0.299.2) isn't a false positive."""
with (
patch("ucode.cli.ensure_bootstrap_dependencies") as mock_bootstrap,
patch("ucode.cli.load_state", return_value=MINIMAL_STATE),
patch("ucode.cli._auto_configure_tool"),
patch("ucode.cli.configure_shared_state", return_value=MINIMAL_STATE),
patch("ucode.cli.ensure_provider_state", return_value=MINIMAL_STATE),
patch(
"ucode.cli.resolve_launch_model",
return_value=(MINIMAL_STATE, "databricks-claude-sonnet-4"),
),
patch("ucode.cli.configure_tool", return_value=MINIMAL_STATE),
patch("ucode.cli._fetch_managed_config", return_value=None),
patch("ucode.cli.launch_agent"),
):
result = runner.invoke(app, ["claude", "--skip-preflight"])
assert result.exit_code == 0, result.output
mock_bootstrap.assert_called_once_with(
"claude", update_existing=False, skip_cli_version_check=True
)


class TestPassthroughArgs:
@pytest.mark.parametrize(
Expand Down
38 changes: 38 additions & 0 deletions tests/test_databricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
ensure_pat_bearer,
get_databricks_token,
install_ai_tools,
install_databricks_cli,
list_databricks_apps,
list_databricks_connections,
list_genie_spaces,
Expand Down Expand Up @@ -1851,6 +1852,43 @@ def test_raises_when_version_unparseable(self, tmp_path, monkeypatch):
ensure_databricks_cli_version()


class TestInstallDatabricksCli:
def test_checks_version_when_present(self, monkeypatch):
monkeypatch.setattr(db_mod.shutil, "which", lambda cmd: "/usr/bin/databricks")
checked = []
monkeypatch.setattr(db_mod, "ensure_databricks_cli_version", lambda: checked.append(True))
install_databricks_cli()
assert checked == [True]

def test_skip_version_check_bypasses_version_gate(self, monkeypatch):
"""`--skip-preflight` sets skip_version_check: an already-installed CLI is
trusted without the minimum-version gate, so a public-preview build is no
longer a false positive."""
monkeypatch.setattr(db_mod.shutil, "which", lambda cmd: "/usr/bin/databricks")
checked = []
monkeypatch.setattr(db_mod, "ensure_databricks_cli_version", lambda: checked.append(True))
install_databricks_cli(skip_version_check=True)
assert checked == []

def test_skip_version_check_still_installs_when_missing(self, monkeypatch):
"""A missing CLI is installed even under skip_version_check — only the
version *check* is bypassed, not the install."""
present = {"databricks": None}
monkeypatch.setattr(db_mod.shutil, "which", lambda cmd: present.get(cmd))
installed = []

def fake_installer(brew_subcommand="install"):
present["databricks"] = "/usr/bin/databricks"
installed.append(brew_subcommand)

monkeypatch.setattr(db_mod, "_run_databricks_cli_installer", fake_installer)
checked = []
monkeypatch.setattr(db_mod, "ensure_databricks_cli_version", lambda: checked.append(True))
install_databricks_cli(skip_version_check=True)
assert installed == ["install"]
assert checked == []


class TestRunDatabricksCliInstaller:
@pytest.mark.parametrize("brew_subcommand", ["install", "upgrade"])
def test_macos_uses_fully_qualified_tap_formula(self, monkeypatch, brew_subcommand):
Expand Down
Loading