Skip to content

Commit f2f10fa

Browse files
Reject mistyped ucode launch flags instead of forwarding them
The launch commands set `ignore_unknown_options=True` so an agent's own flags pass straight through. That also meant a *mistyped* ucode flag — e.g. `--skip-preflight-checks` instead of `--skip-preflight` — was silently handed to the agent, where it does nothing, so `--skip-preflight` appeared not to work. Add a guard that runs before launch and rejects any passthrough arg whose bare name is a near-miss of a known ucode launch flag (a superstring like `--skip-preflight-checks`, or a near-complete truncation), with an error naming the intended flag. Unrelated agent flags (`--model`, `-r`, `--dangerously-skip-permissions`, …) are left untouched and still pass through. Co-authored-by: Isaac
1 parent 1c506ed commit f2f10fa

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

src/ucode/cli.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,51 @@ def _print_budget_panel(recommendation: dict, tool: str, managed: dict | None =
13591359
console.print(panel)
13601360

13611361

1362+
# ucode's own launch flags. The launch commands set `ignore_unknown_options`
1363+
# so the agent's own flags pass straight through, which also means a *mistyped*
1364+
# ucode flag (e.g. `--skip-preflight-checks`) is silently forwarded to the agent
1365+
# instead of taking effect. We catch near-misses of these up front so a typo
1366+
# fails loudly rather than quietly running with the flag ignored.
1367+
_UCODE_LAUNCH_FLAGS = frozenset(
1368+
{
1369+
"--skip-preflight",
1370+
"--workspace",
1371+
"--provider",
1372+
"--enable-smart-routing",
1373+
"--disable-smart-routing",
1374+
}
1375+
)
1376+
1377+
1378+
def _reject_mistyped_ucode_flag(ctx: typer.Context) -> None:
1379+
"""Error on a passthrough arg that looks like a misspelled ucode launch flag.
1380+
1381+
Only flags whose bare name (before any `=`) is a near-miss of a known ucode
1382+
flag are rejected — an unrelated agent flag like `--model` is left alone. This
1383+
keeps `--skip-preflight-checks` from being silently handed to the agent (where
1384+
it does nothing) instead of enabling the ucode behavior the user intended.
1385+
"""
1386+
for raw in ctx.args:
1387+
if not raw.startswith("--"):
1388+
continue
1389+
name = raw.split("=", 1)[0]
1390+
if name in _UCODE_LAUNCH_FLAGS:
1391+
continue
1392+
for known in _UCODE_LAUNCH_FLAGS:
1393+
# A superstring is the reported bug: `--skip-preflight-checks` starts
1394+
# with `--skip-preflight`. A near-complete truncation (`--skip-prefligh`,
1395+
# one char short) is caught too — but a short generic prefix like
1396+
# `--enable` is left alone so a real agent flag isn't hijacked.
1397+
superstring = name.startswith(known)
1398+
truncation = known.startswith(name) and len(known) - len(name) <= 3
1399+
if superstring or truncation:
1400+
raise RuntimeError(
1401+
f"Unknown ucode option '{name}'. Did you mean '{known}'? "
1402+
"(ucode flags must be spelled exactly; anything else is passed "
1403+
"through to the agent.)"
1404+
)
1405+
1406+
13621407
def _launch_tool(
13631408
tool_name: str,
13641409
ctx: typer.Context,
@@ -1370,6 +1415,7 @@ def _launch_tool(
13701415
recommendation: dict | None = None,
13711416
) -> None:
13721417
try:
1418+
_reject_mistyped_ucode_flag(ctx)
13731419
tool = normalize_tool(tool_name)
13741420
# An explicit --workspace targets that workspace for this launch (and
13751421
# auto-configures it if unseen), so `ucode claude --provider ... --workspace ...`

tests/test_cli.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,70 @@ def test_skip_preflight_bypasses_cli_version_check(self):
820820
)
821821

822822

823+
class TestMistypedUcodeFlag:
824+
"""A launch command sets `ignore_unknown_options`, so a mistyped ucode flag
825+
would otherwise be forwarded to the agent and silently do nothing. The guard
826+
turns near-misses of ucode's own flags into a loud error."""
827+
828+
def test_superstring_typo_is_rejected(self):
829+
# The reported bug: `--skip-preflight-checks` never enabled the behavior.
830+
with patch("ucode.cli.ensure_bootstrap_dependencies") as mock_bootstrap:
831+
result = runner.invoke(app, ["claude", "--skip-preflight-checks"])
832+
assert result.exit_code == 1
833+
out = _strip_ansi(result.output)
834+
assert "--skip-preflight-checks" in out
835+
assert "--skip-preflight" in out
836+
# Fails before any bootstrap/install work runs.
837+
mock_bootstrap.assert_not_called()
838+
839+
def test_plural_workspace_typo_is_rejected(self):
840+
result = runner.invoke(app, ["codex", "--workspaces", "https://ws"])
841+
assert result.exit_code == 1
842+
assert "--workspace" in _strip_ansi(result.output)
843+
844+
def test_correct_flag_is_not_rejected(self):
845+
with (
846+
patch("ucode.cli.ensure_bootstrap_dependencies"),
847+
patch("ucode.cli.load_state", return_value=MINIMAL_STATE),
848+
patch("ucode.cli._auto_configure_tool"),
849+
patch("ucode.cli.configure_shared_state", return_value=MINIMAL_STATE),
850+
patch("ucode.cli.ensure_provider_state", return_value=MINIMAL_STATE),
851+
patch(
852+
"ucode.cli.resolve_launch_model",
853+
return_value=(MINIMAL_STATE, "databricks-claude-sonnet-4"),
854+
),
855+
patch("ucode.cli.configure_tool", return_value=MINIMAL_STATE),
856+
patch("ucode.cli._fetch_managed_config", return_value=None),
857+
patch("ucode.cli.launch_agent"),
858+
):
859+
result = runner.invoke(app, ["claude", "--skip-preflight"])
860+
assert result.exit_code == 0, result.output
861+
862+
def test_unrelated_agent_flag_passes_through(self):
863+
# A real agent flag that isn't a near-miss of any ucode flag reaches the agent.
864+
captured = {}
865+
with (
866+
patch("ucode.cli.ensure_bootstrap_dependencies"),
867+
patch("ucode.cli.load_state", return_value=MINIMAL_STATE),
868+
patch("ucode.cli._auto_configure_tool"),
869+
patch("ucode.cli.configure_shared_state", return_value=MINIMAL_STATE),
870+
patch("ucode.cli.ensure_provider_state", return_value=MINIMAL_STATE),
871+
patch(
872+
"ucode.cli.resolve_launch_model",
873+
return_value=(MINIMAL_STATE, "databricks-claude-sonnet-4"),
874+
),
875+
patch("ucode.cli.configure_tool", return_value=MINIMAL_STATE),
876+
patch("ucode.cli._fetch_managed_config", return_value=None),
877+
patch(
878+
"ucode.cli.launch_agent",
879+
side_effect=lambda tool, state, args: captured.setdefault("args", args),
880+
),
881+
):
882+
result = runner.invoke(app, ["claude", "--model", "opus"])
883+
assert result.exit_code == 0, result.output
884+
assert "--model" in captured.get("args", [])
885+
886+
823887
class TestPassthroughArgs:
824888
@pytest.mark.parametrize(
825889
"tool,extra_args",

0 commit comments

Comments
 (0)