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
5 changes: 4 additions & 1 deletion docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ defaults to `claude` and can be overridden with `CLAUDE_CLI_BIN`.
| `model.qwen_chat_*` | Shared `base_url`, `api_key`, `temperature`, `timeout_seconds`, `max_tokens`, and `enable_thinking` |
| `model.optimizer_qwen_chat_*` / `model.target_qwen_chat_*` | Per-role Qwen overrides |
| `model.minimax_*` | MiniMax `base_url`, `api_key`, shared `minimax_model`, `temperature`, `max_tokens`, and `enable_thinking`; `minimax_model` applies when MiniMax is the target |
| `model.codex_exec_*` | Codex path, sandbox, profile, SDK mode, reasoning, network/search, and approval policy |
| `model.codex_exec_*` | Codex path (`model.codex_cli_bin` and `model.codex_path` aliases accepted), sandbox (`model.sandbox` and `model.codex_sandbox` aliases accepted), profile, SDK mode, reasoning, network/search, and approval policy |
| `model.claude_code_exec_*` | Claude path, profile, SDK mode, effort, and thinking-token cap |

> [!WARNING]
> When setting the Codex sandbox mode to `danger-full-access` (or using the `CODEX_SANDBOX_MODE="danger-full-access"` environment alias), the model is granted complete and unrestricted write access to the host filesystem. Use this mode with extreme caution, preferably only within an isolated container environment.
| `model.cursor_exec_path` | Cursor Agent executable path; default `cursor-agent` |
| `model.cursor_exec_sandbox` | Cursor sandbox mode: `enabled` (default) or `disabled`; file-edit rollouts require `enabled` |
| `model.copilot_exec_path` | GitHub Copilot CLI executable path; default `copilot` |
Expand Down
18 changes: 9 additions & 9 deletions scripts/eval_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,15 +488,15 @@ def _set_role(key: str, value: str) -> None:
set_optimizer_deployment(cfg.get("optimizer_model", default_model_for_backend(backend)))
set_target_deployment(cfg.get("target_model", default_model_for_backend(backend)))
configure_codex_exec(
path=cfg.get("codex_exec_path", "codex"),
sandbox=cfg.get("codex_exec_sandbox", "workspace-write"),
profile=cfg.get("codex_exec_profile", ""),
full_auto=cfg.get("codex_exec_full_auto", False),
reasoning_effort=cfg.get("codex_exec_reasoning_effort", "none"),
use_sdk=cfg.get("codex_exec_use_sdk", None),
network_access=cfg.get("codex_exec_network_access", False),
web_search=cfg.get("codex_exec_web_search", False),
approval_policy=cfg.get("codex_exec_approval_policy", "never"),
path=cfg.get("codex_exec_path") or cfg.get("codex_path") or cfg.get("codex_cli_bin") or cfg.get("codex_bin") or None,
sandbox=cfg.get("codex_exec_sandbox") or cfg.get("sandbox") or cfg.get("codex_sandbox") or None,
profile=cfg.get("codex_exec_profile") or None,
full_auto=cfg.get("codex_exec_full_auto"),
reasoning_effort=cfg.get("codex_exec_reasoning_effort") or None,
use_sdk=cfg.get("codex_exec_use_sdk"),
network_access=cfg.get("codex_exec_network_access"),
web_search=cfg.get("codex_exec_web_search"),
approval_policy=cfg.get("codex_exec_approval_policy") or None,
)
configure_claude_code_exec(
path=cfg.get("claude_code_exec_path", "claude"),
Expand Down
4 changes: 4 additions & 0 deletions skillopt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
"model.rewrite_reasoning_effort": "rewrite_reasoning_effort",
"model.rewrite_max_completion_tokens": "rewrite_max_completion_tokens",
"model.codex_exec_path": "codex_exec_path",
"model.codex_path": "codex_exec_path",
"model.codex_cli_bin": "codex_exec_path",
"model.codex_exec_sandbox": "codex_exec_sandbox",
"model.codex_sandbox": "codex_exec_sandbox",
"model.sandbox": "codex_exec_sandbox",
"model.codex_exec_profile": "codex_exec_profile",
"model.codex_exec_full_auto": "codex_exec_full_auto",
"model.codex_exec_reasoning_effort": "codex_exec_reasoning_effort",
Expand Down
18 changes: 9 additions & 9 deletions skillopt/engine/trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,15 +747,15 @@ def _build_eval_env(split: str, env_num: int, seed: int):
set_optimizer_deployment(cfg["optimizer_model"])
set_target_deployment(cfg["target_model"])
configure_codex_exec(
path=cfg.get("codex_exec_path", "codex"),
sandbox=cfg.get("codex_exec_sandbox", "workspace-write"),
profile=cfg.get("codex_exec_profile", ""),
full_auto=cfg.get("codex_exec_full_auto", False),
reasoning_effort=cfg.get("codex_exec_reasoning_effort", "none"),
use_sdk=cfg.get("codex_exec_use_sdk", None),
network_access=cfg.get("codex_exec_network_access", False),
web_search=cfg.get("codex_exec_web_search", False),
approval_policy=cfg.get("codex_exec_approval_policy", "never"),
path=cfg.get("codex_exec_path") or cfg.get("codex_path") or cfg.get("codex_cli_bin") or cfg.get("codex_bin") or None,
sandbox=cfg.get("codex_exec_sandbox") or cfg.get("sandbox") or cfg.get("codex_sandbox") or None,
profile=cfg.get("codex_exec_profile") or None,
full_auto=cfg.get("codex_exec_full_auto"),
reasoning_effort=cfg.get("codex_exec_reasoning_effort") or None,
use_sdk=cfg.get("codex_exec_use_sdk"),
network_access=cfg.get("codex_exec_network_access"),
web_search=cfg.get("codex_exec_web_search"),
approval_policy=cfg.get("codex_exec_approval_policy") or None,
)
configure_claude_code_exec(
path=cfg.get("claude_code_exec_path", "claude"),
Expand Down
6 changes: 4 additions & 2 deletions skillopt/model/backend_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def _parse_bool(value: str | None, default: bool) -> bool:
OPTIMIZER_BACKEND = normalize_backend_name(os.environ.get("OPTIMIZER_BACKEND", "openai_chat"))
TARGET_BACKEND = normalize_backend_name(os.environ.get("TARGET_BACKEND", "openai_chat"))

CODEX_EXEC_PATH = os.environ.get("CODEX_EXEC_PATH", "codex")
CODEX_EXEC_SANDBOX = os.environ.get("CODEX_EXEC_SANDBOX", "workspace-write")
CODEX_EXEC_PATH = os.environ.get("CODEX_EXEC_PATH") or os.environ.get("CODEX_CLI_BIN") or os.environ.get("CODEX_PATH") or "codex"
CODEX_EXEC_SANDBOX = os.environ.get("CODEX_EXEC_SANDBOX") or os.environ.get("CODEX_SANDBOX_MODE") or os.environ.get("CODEX_SANDBOX") or "workspace-write"
CODEX_EXEC_PROFILE = os.environ.get("CODEX_EXEC_PROFILE", "")
CODEX_EXEC_FULL_AUTO = _parse_bool(os.environ.get("CODEX_EXEC_FULL_AUTO"), True)
CODEX_EXEC_REASONING_EFFORT = os.environ.get("CODEX_EXEC_REASONING_EFFORT", "none")
Expand Down Expand Up @@ -133,9 +133,11 @@ def configure_codex_exec(
if path is not None:
CODEX_EXEC_PATH = str(path).strip() or "codex"
os.environ["CODEX_EXEC_PATH"] = CODEX_EXEC_PATH
os.environ["CODEX_CLI_BIN"] = CODEX_EXEC_PATH
if sandbox is not None:
CODEX_EXEC_SANDBOX = str(sandbox).strip() or "workspace-write"
os.environ["CODEX_EXEC_SANDBOX"] = CODEX_EXEC_SANDBOX
os.environ["CODEX_SANDBOX_MODE"] = CODEX_EXEC_SANDBOX
if profile is not None:
CODEX_EXEC_PROFILE = str(profile).strip()
os.environ["CODEX_EXEC_PROFILE"] = CODEX_EXEC_PROFILE
Expand Down
6 changes: 3 additions & 3 deletions skillopt/model/codex_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
)


CODEX_BIN = os.environ.get("CODEX_CLI_BIN", "codex")
CODEX_PROFILE = os.environ.get("CODEX_PROFILE", "review")
CODEX_SANDBOX_MODE = os.environ.get("CODEX_SANDBOX_MODE", "read-only")
CODEX_BIN = os.environ.get("CODEX_EXEC_PATH") or os.environ.get("CODEX_CLI_BIN") or "codex"
CODEX_PROFILE = os.environ.get("CODEX_EXEC_PROFILE") or os.environ.get("CODEX_PROFILE") or "review"
CODEX_SANDBOX_MODE = os.environ.get("CODEX_EXEC_SANDBOX") or os.environ.get("CODEX_SANDBOX_MODE") or "read-only"

OPTIMIZER_DEPLOYMENT = os.environ.get("OPTIMIZER_DEPLOYMENT", "gpt-4o")
TARGET_DEPLOYMENT = os.environ.get("TARGET_DEPLOYMENT", "gpt-4o")
Expand Down
10 changes: 6 additions & 4 deletions skillopt/model/codex_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -955,10 +955,12 @@ def _run_codex_cli_exec(
cmd.extend(["-c", f'model_reasoning_effort="{reasoning_effort}"'])
actual_full_auto = bool(config.get("full_auto", True)) if full_auto is None else bool(full_auto)
actual_sandbox = str(sandbox or config["sandbox"])
if actual_full_auto:
cmd.append("--full-auto")
else:
cmd.extend(["--sandbox", actual_sandbox])
cmd.extend(["--sandbox", actual_sandbox])

approval_policy = str(config.get("approval_policy", "never"))
if not actual_full_auto and approval_policy == "never":
approval_policy = "ask"
cmd.extend(["--approval-policy", approval_policy])
if model:
cmd.extend(["-m", model])
for data_dir in data_dirs or []:
Expand Down
85 changes: 85 additions & 0 deletions tests/test_codex_config_aliases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import os
from unittest import mock

from skillopt.config import flatten_config
from skillopt.model.backend_config import configure_codex_exec


def test_codex_config_aliases_flatten_config():
structured_cfg = {
"model": {
"sandbox": "danger-full-access",
"codex_cli_bin": "/custom/path/codex"
}
}
flat = flatten_config(structured_cfg)
assert flat["codex_exec_sandbox"] == "danger-full-access"
assert flat["codex_exec_path"] == "/custom/path/codex"


def test_codex_backend_env_aliases(monkeypatch):
monkeypatch.setenv("CODEX_CLI_BIN", "/env/path/codex")
monkeypatch.setenv("CODEX_SANDBOX_MODE", "danger-full-access-env")

import importlib

import skillopt.model.backend_config
import skillopt.model.codex_backend

importlib.reload(skillopt.model.backend_config)
importlib.reload(skillopt.model.codex_backend)

assert skillopt.model.codex_backend.CODEX_BIN == "/env/path/codex"
assert skillopt.model.codex_backend.CODEX_SANDBOX_MODE == "danger-full-access-env"


def test_configure_codex_exec_sets_aliases(monkeypatch):
monkeypatch.setattr("skillopt.model.backend_config.CODEX_EXEC_PATH", "")
monkeypatch.setattr("skillopt.model.backend_config.CODEX_EXEC_SANDBOX", "")
monkeypatch.setattr("skillopt.model.backend_config.CODEX_EXEC_APPROVAL_POLICY", "")

configure_codex_exec(path="/configured/codex", sandbox="configured-sandbox")

assert os.environ["CODEX_EXEC_PATH"] == "/configured/codex"
assert os.environ["CODEX_CLI_BIN"] == "/configured/codex"

assert os.environ["CODEX_EXEC_SANDBOX"] == "configured-sandbox"
assert os.environ["CODEX_SANDBOX_MODE"] == "configured-sandbox"


@mock.patch("skillopt.model.codex_harness.subprocess.run")
def test_codex_harness_sandbox_passed_with_full_auto(mock_run, monkeypatch):
from skillopt.model.codex_harness import _run_codex_cli_exec

# Isolate global config
monkeypatch.setattr("skillopt.model.backend_config.CODEX_EXEC_SANDBOX", "")
monkeypatch.setattr("skillopt.model.backend_config.CODEX_EXEC_FULL_AUTO", False)
monkeypatch.setattr("skillopt.model.backend_config.CODEX_EXEC_APPROVAL_POLICY", "ask")

configure_codex_exec(sandbox="danger-full-access", full_auto=True, approval_policy="never")

mock_proc = mock.MagicMock()
mock_proc.returncode = 0
mock_proc.stdout = ""
mock_proc.stderr = ""
mock_run.return_value = mock_proc

with mock.patch("skillopt.model.codex_harness._persist_codex_artifacts"), \
mock.patch("skillopt.model.azure_openai.tracker.record", create=True):
_run_codex_cli_exec(
work_dir=".",
prompt="test",
model="test-model",
timeout=10,
)

mock_run.assert_called_once()
args = mock_run.call_args[0][0]

assert "--sandbox" in args
sandbox_idx = args.index("--sandbox")
assert args[sandbox_idx + 1] == "danger-full-access"

assert "--approval-policy" in args
approval_idx = args.index("--approval-policy")
assert args[approval_idx + 1] == "never"