fix(codex): hardcode exec flags (#75)

This commit is contained in:
banteg
2026-01-09 23:08:48 +04:00
committed by GitHub
parent e7ef89b4b5
commit 7ffb99d779
3 changed files with 56 additions and 14 deletions
+2
View File
@@ -68,6 +68,8 @@ voice_transcription = true
[codex] [codex]
# optional: profile from ~/.codex/config.toml # optional: profile from ~/.codex/config.toml
profile = "takopi" profile = "takopi"
# optional: extra codex CLI args (exec flags are managed by Takopi)
# extra_args = ["-c", "notify=[]"]
[claude] [claude]
model = "sonnet" model = "sonnet"
+34 -11
View File
@@ -25,18 +25,29 @@ _RECONNECTING_RE = re.compile(
r"^Reconnecting\.{3}\s*(?P<attempt>\d+)/(?P<max>\d+)\s*$", r"^Reconnecting\.{3}\s*(?P<attempt>\d+)/(?P<max>\d+)\s*$",
re.IGNORECASE, re.IGNORECASE,
) )
_EXEC_ONLY_FLAGS = {"--skip-git-repo-check"} _EXEC_ONLY_FLAGS = {
"--skip-git-repo-check",
"--json",
"--output-schema",
"--output-last-message",
"--color",
"-o",
}
_EXEC_ONLY_PREFIXES = (
"--output-schema=",
"--output-last-message=",
"--color=",
)
def _split_exec_flags(extra_args: list[str]) -> tuple[list[str], list[str]]: def _find_exec_only_flag(extra_args: list[str]) -> str | None:
base_args: list[str] = []
exec_args: list[str] = []
for arg in extra_args: for arg in extra_args:
if arg in _EXEC_ONLY_FLAGS: if arg in _EXEC_ONLY_FLAGS:
exec_args.append(arg) return arg
else: for prefix in _EXEC_ONLY_PREFIXES:
base_args.append(arg) if arg.startswith(prefix):
return base_args, exec_args return arg
return None
def _parse_reconnect_message(message: str) -> tuple[int, int] | None: def _parse_reconnect_message(message: str) -> tuple[int, int] | None:
@@ -409,8 +420,13 @@ class CodexRunner(ResumeTokenMixin, JsonlSubprocessRunner):
state: Any, state: Any,
) -> list[str]: ) -> list[str]:
_ = prompt, state _ = prompt, state
base_args, exec_args = _split_exec_flags(self.extra_args) args = [
args = [*base_args, "exec", *exec_args, "--json"] *self.extra_args,
"exec",
"--json",
"--skip-git-repo-check",
"--color=never",
]
if resume: if resume:
args.extend(["resume", resume.value, "-"]) args.extend(["resume", resume.value, "-"])
else: else:
@@ -585,7 +601,7 @@ def build_runner(config: EngineConfig, config_path: Path) -> Runner:
extra_args_value = config.get("extra_args") extra_args_value = config.get("extra_args")
if extra_args_value is None: if extra_args_value is None:
extra_args = ["-c", "notify=[]", "--skip-git-repo-check"] extra_args = ["-c", "notify=[]"]
elif isinstance(extra_args_value, list) and all( elif isinstance(extra_args_value, list) and all(
isinstance(item, str) for item in extra_args_value isinstance(item, str) for item in extra_args_value
): ):
@@ -595,6 +611,13 @@ def build_runner(config: EngineConfig, config_path: Path) -> Runner:
f"Invalid `codex.extra_args` in {config_path}; expected a list of strings." f"Invalid `codex.extra_args` in {config_path}; expected a list of strings."
) )
exec_only_flag = _find_exec_only_flag(extra_args)
if exec_only_flag:
raise ConfigError(
f"Invalid `codex.extra_args` in {config_path}; exec-only flag "
f"{exec_only_flag!r} is managed by Takopi."
)
title = "Codex" title = "Codex"
profile_value = config.get("profile") profile_value = config.get("profile")
if profile_value: if profile_value:
+20 -3
View File
@@ -12,7 +12,7 @@ from takopi.model import (
StartedEvent, StartedEvent,
TakopiEvent, TakopiEvent,
) )
from takopi.runners.codex import CodexRunner from takopi.runners.codex import CodexRunner, _find_exec_only_flag
CODEX_ENGINE = EngineId("codex") CODEX_ENGINE = EngineId("codex")
@@ -131,7 +131,7 @@ async def test_run_allows_parallel_different_sessions() -> None:
def test_codex_exec_flags_after_exec() -> None: def test_codex_exec_flags_after_exec() -> None:
runner = CodexRunner( runner = CodexRunner(
codex_cmd="codex", codex_cmd="codex",
extra_args=["-c", "notify=[]", "--skip-git-repo-check"], extra_args=["-c", "notify=[]"],
) )
state = runner.new_state("hi", None) state = runner.new_state("hi", None)
args = runner.build_args("hi", None, state=state) args = runner.build_args("hi", None, state=state)
@@ -139,12 +139,29 @@ def test_codex_exec_flags_after_exec() -> None:
"-c", "-c",
"notify=[]", "notify=[]",
"exec", "exec",
"--skip-git-repo-check",
"--json", "--json",
"--skip-git-repo-check",
"--color=never",
"-", "-",
] ]
@pytest.mark.parametrize(
("extra_args", "expected"),
[
([], None),
(["-c", "notify=[]"], None),
(["--skip-git-repo-check"], "--skip-git-repo-check"),
(["--color=never"], "--color=never"),
(["--output-schema", "schema.json"], "--output-schema"),
(["--output-last-message=out.txt"], "--output-last-message=out.txt"),
(["-o", "out.txt"], "-o"),
],
)
def test_find_exec_only_flag(extra_args: list[str], expected: str | None) -> None:
assert _find_exec_only_flag(extra_args) == expected
@pytest.mark.anyio @pytest.mark.anyio
async def test_run_serializes_new_session_after_session_is_known( async def test_run_serializes_new_session_after_session_is_known(
tmp_path, monkeypatch tmp_path, monkeypatch