Skip to content

Commit bcff2ab

Browse files
committed
codex/ucode: exec real binary for non-interactive subcommands, skip init
ucode's launch runs full session init on every invocation — auth validation, per-launch model discovery, config write — before exec'ing the agent. For non-interactive invocations that need none of it, this only adds latency and can corrupt output: - `codex app-server` is a server subcommand the *caller* configures (e.g. omnigent supplies a private CODEX_HOME + its own provider config); ucode's init adds several seconds and delays the socket bind past the caller's readiness budget, so the app-server times out. It also rejects the global `--profile` ucode injects. - `<agent> --version` / `--help` are info commands; init delays them, and ucode's own banner/version can be parsed by a caller instead of the agent's. Add a passthrough: for a universal info flag (--version/-V/--help/-h) or a tool-declared non-interactive subcommand (an agent module's PASSTHROUGH_SUBCOMMANDS; codex -> app-server), exec the real binary directly with no ucode flags or init. Extensible: add a flag to _PASSTHROUGH_GLOBAL_FLAGS, or a PASSTHROUGH_SUBCOMMANDS set + subcommand() to an agent module. This supersedes the earlier codex --profile-skip for app-server — the passthrough runs before launch(), so ucode never adds --profile there — so launch() goes back to always attaching --profile for the runtime subcommands that accept it. Co-authored-by: Isaac
1 parent 57aeb05 commit bcff2ab

4 files changed

Lines changed: 188 additions & 0 deletions

File tree

src/ucode/agents/__init__.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
map_bedrock_claude_models,
2525
resolve_provider_service,
2626
)
27+
from ucode.launcher import exec_or_spawn
2728
from ucode.state import get_provider_service, load_state, save_state
2829
from ucode.telemetry import agent_version
2930
from ucode.ui import (
@@ -65,6 +66,13 @@
6566
DEFAULT_TOOL = "codex"
6667
BUNDLE_VERSION = 1
6768

69+
# Global flags that only print info and launch no session — for ANY tool, exec
70+
# the real binary directly, skipping ucode init. A ``codex --version`` probe
71+
# must be both fast (init runs network validation + model discovery) and clean
72+
# (ucode's own banner/version can otherwise be parsed by a caller instead of the
73+
# agent's). Extend by adding a flag here.
74+
_PASSTHROUGH_GLOBAL_FLAGS = frozenset({"--version", "-V", "--help", "-h"})
75+
6876

6977
def normalize_tool(tool: str) -> str:
7078
normalized = TOOL_ALIASES.get(tool.strip().lower())
@@ -321,6 +329,42 @@ def launch(tool: str, state: dict, tool_args: list[str]) -> None:
321329
_MODULES[tool].launch(state, tool_args)
322330

323331

332+
def is_passthrough_invocation(tool: str, tool_args: list[str]) -> bool:
333+
"""Whether *tool_args* is a non-interactive invocation that needs no ucode
334+
init and should exec the real binary directly (see :func:`passthrough`).
335+
336+
Two extensible cases:
337+
* a universal info flag (``--version``/``--help``, see
338+
:data:`_PASSTHROUGH_GLOBAL_FLAGS`), or
339+
* a tool-declared non-interactive subcommand — the agent module's
340+
``PASSTHROUGH_SUBCOMMANDS``, located via its ``subcommand()`` parser —
341+
e.g. codex ``app-server``, which the caller configures itself.
342+
343+
Extend by adding a flag above, or a ``PASSTHROUGH_SUBCOMMANDS`` set +
344+
``subcommand()`` to an agent module.
345+
"""
346+
if any(arg in _PASSTHROUGH_GLOBAL_FLAGS for arg in tool_args):
347+
return True
348+
module = _MODULES[tool]
349+
subcommands = getattr(module, "PASSTHROUGH_SUBCOMMANDS", frozenset())
350+
if not subcommands:
351+
return False
352+
resolve = getattr(module, "subcommand", None)
353+
return resolve is not None and resolve(tool_args) in subcommands
354+
355+
356+
def passthrough(tool: str, tool_args: list[str]) -> None:
357+
"""Exec the real tool binary directly, skipping all ucode init/config.
358+
359+
For the non-interactive invocations :func:`is_passthrough_invocation`
360+
matches: an info flag needs nothing, and a passthrough subcommand is
361+
configured by its caller (auth/gateway/model/policies). No ucode flags are
362+
injected. Relies on the self-healing wrapper's recursion sentinel to reach
363+
the real binary, exactly like :func:`launch`.
364+
"""
365+
exec_or_spawn([TOOL_SPECS[tool]["binary"], *tool_args])
366+
367+
324368
def check_gateway_endpoint(state: dict, tool: str) -> bool:
325369
"""V2-only: a tool is available iff we discovered models for it."""
326370
if tool == "claude":

src/ucode/agents/codex.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,71 @@ def _gpt_version_key(entry: tuple[str, tuple[int, int | None, int | None, str]])
391391
return max(parsed, key=_gpt_version_key)[0]
392392

393393

394+
# Non-interactive subcommands that launch no interactive session and are
395+
# configured by the CALLER, not ucode — e.g. omnigent runs ``codex app-server``
396+
# with a private CODEX_HOME + its own provider config. ucode's launch init
397+
# (auth validation, per-launch model discovery, config write) is redundant for
398+
# these, and its latency overruns the caller's app-server readiness budget — so
399+
# ucode execs the real binary directly, skipping init (see
400+
# ``agents.is_passthrough_invocation``). This also sidesteps ``--profile``
401+
# entirely: ``codex app-server`` rejects the global ``--profile`` ("--profile
402+
# only applies to runtime commands and codex mcp"), and the passthrough — which
403+
# runs before ``launch`` — never adds it.
404+
PASSTHROUGH_SUBCOMMANDS = frozenset({"app-server"})
405+
406+
# codex global options that consume the following token as their value. Used to
407+
# skip past a leading global (e.g. ``--model X``) when locating the subcommand,
408+
# so it isn't mistaken for the subcommand itself. Boolean flags take no value;
409+
# ``--flag=value`` forms are self-contained and handled inline.
410+
_CODEX_GLOBAL_VALUE_FLAGS = frozenset(
411+
{
412+
"-c",
413+
"--config",
414+
"-i",
415+
"--image",
416+
"-m",
417+
"--model",
418+
"-p",
419+
"--profile",
420+
"-s",
421+
"--sandbox",
422+
"-C",
423+
"--cd",
424+
"-a",
425+
"--ask-for-approval",
426+
"--enable",
427+
"--disable",
428+
"--remote",
429+
"--remote-auth-token-env",
430+
"--local-provider",
431+
"--add-dir",
432+
}
433+
)
434+
435+
436+
def _codex_subcommand(tool_args: list[str]) -> str | None:
437+
"""Return the codex subcommand in *tool_args*, skipping global options.
438+
439+
``None`` when there is no subcommand (the interactive TUI).
440+
"""
441+
i = 0
442+
while i < len(tool_args):
443+
arg = tool_args[i]
444+
if not arg.startswith("-"):
445+
return arg
446+
# A bare value flag (``--model X``) consumes the next token too; a
447+
# ``--flag=value`` form or a boolean flag consumes only itself.
448+
if "=" not in arg and arg in _CODEX_GLOBAL_VALUE_FLAGS:
449+
i += 1
450+
i += 1
451+
return None
452+
453+
454+
# Public alias so cross-agent dispatch (agents.is_passthrough_invocation) can
455+
# resolve the codex subcommand without reaching into a private helper.
456+
subcommand = _codex_subcommand
457+
458+
394459
def launch(state: dict, tool_args: list[str]) -> None:
395460
binary = SPEC["binary"]
396461
workspace = state.get("workspace")

src/ucode/cli.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
ensure_bootstrap_dependencies,
1818
ensure_provider_state,
1919
install_tool_binary,
20+
is_passthrough_invocation,
2021
normalize_tool,
22+
passthrough,
2123
provider_permission_error,
2224
resolve_launch_model,
2325
resolve_provider_models,
@@ -825,6 +827,16 @@ def _auto_configure_tool(tool: str) -> None:
825827
def _launch_tool(tool_name: str, ctx: typer.Context, provider: str | None = None) -> None:
826828
try:
827829
tool = normalize_tool(tool_name)
830+
# Non-interactive invocations — a universal info flag (--version/--help)
831+
# or a tool-declared non-interactive subcommand (e.g. codex app-server,
832+
# which the caller configures itself) — need none of ucode's session
833+
# init (auth validation, per-launch model discovery, config write). That
834+
# init only delays the output and can even corrupt a caller's
835+
# `--version` probe (ucode's banner/version parsed instead of the
836+
# agent's). Exec the real binary directly.
837+
if is_passthrough_invocation(tool, ctx.args):
838+
passthrough(tool, ctx.args)
839+
return
828840
existing = load_state()
829841
# Workspaces configured with --use-pat export the profile's PAT as
830842
# DATABRICKS_BEARER up front so every auth check below (and the

tests/test_agent_codex.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66

7+
from ucode import agents
78
from ucode.agents import codex
89
from ucode.config_io import read_toml_safe
910

@@ -436,3 +437,69 @@ def fake_execvp(binary: str, args: list[str]) -> None:
436437

437438
assert os.environ["OAUTH_TOKEN"] == "fresh-token"
438439
assert exec_calls == [("codex", ["codex", "--profile", "ucode", "--search"])]
440+
441+
def test_keeps_profile_for_exec_subcommand(self, monkeypatch):
442+
exec_calls: list[tuple[str, list[str]]] = []
443+
444+
def fake_execvp(binary: str, args: list[str]) -> None:
445+
exec_calls.append((binary, args))
446+
raise RuntimeError("stop")
447+
448+
monkeypatch.setattr(codex, "get_databricks_token", lambda workspace, profile=None: "tok")
449+
monkeypatch.setattr(os, "execvp", fake_execvp)
450+
451+
try:
452+
codex.launch({"workspace": WS}, ["exec", "say hi"])
453+
except RuntimeError:
454+
pass
455+
456+
assert exec_calls == [("codex", ["codex", "--profile", "ucode", "exec", "say hi"])]
457+
458+
459+
class TestCodexSubcommand:
460+
def test_none_for_interactive(self):
461+
assert codex._codex_subcommand([]) is None
462+
assert codex._codex_subcommand(["--search"]) is None
463+
464+
def test_finds_bare_subcommand(self):
465+
assert codex._codex_subcommand(["exec", "hi"]) == "exec"
466+
assert codex._codex_subcommand(["app-server"]) == "app-server"
467+
468+
def test_skips_leading_value_flags(self):
469+
assert codex._codex_subcommand(["--model", "gpt-5", "app-server"]) == "app-server"
470+
assert codex._codex_subcommand(["-c", "k=v", "exec"]) == "exec"
471+
472+
473+
class TestPassthroughInvocation:
474+
def test_app_server_is_passthrough(self):
475+
# codex app-server is caller-configured (omnigent) and rejects --profile;
476+
# ucode execs it directly, skipping init — even behind a leading global.
477+
assert agents.is_passthrough_invocation("codex", ["app-server", "--listen", "u"])
478+
assert agents.is_passthrough_invocation("codex", ["--model", "gpt-5", "app-server"])
479+
480+
def test_info_flags_passthrough_for_any_tool(self):
481+
for tool in ("codex", "claude"):
482+
assert agents.is_passthrough_invocation(tool, ["--version"])
483+
assert agents.is_passthrough_invocation(tool, ["-V"])
484+
assert agents.is_passthrough_invocation(tool, ["--help"])
485+
assert agents.is_passthrough_invocation(tool, ["-h"])
486+
487+
def test_interactive_and_exec_are_not_passthrough(self):
488+
assert not agents.is_passthrough_invocation("codex", [])
489+
assert not agents.is_passthrough_invocation("codex", ["exec", "say hi"])
490+
# a prompt that merely mentions app-server is not the subcommand
491+
assert not agents.is_passthrough_invocation("codex", ["exec", "run the app-server"])
492+
assert not agents.is_passthrough_invocation("claude", [])
493+
494+
def test_claude_has_no_passthrough_subcommands(self):
495+
# claude has no non-interactive server subcommand; only info flags apply.
496+
assert not agents.is_passthrough_invocation("claude", ["mcp", "list"])
497+
498+
def test_passthrough_execs_real_binary_without_ucode_flags(self, monkeypatch):
499+
calls: list[list[str]] = []
500+
monkeypatch.setattr(agents, "exec_or_spawn", lambda argv: calls.append(argv))
501+
agents.passthrough("codex", ["app-server", "--listen", "u"])
502+
assert calls == [["codex", "app-server", "--listen", "u"]]
503+
504+
def test_handles_equals_form(self):
505+
assert codex._codex_subcommand(["--model=gpt-5", "app-server"]) == "app-server"

0 commit comments

Comments
 (0)