Skip to content
Merged
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
6 changes: 5 additions & 1 deletion docs/reference/trajectory-assessment.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ not independent verification of a model's translation or judgments.
The Codex observer reuses native Chat's shared authenticated HTTP transport.
Credentials and refresh remain with Codex's existing signed-in account; the
observer neither copies tokens nor imports user tool/rule configuration. Its
explicit read-only sandbox, disabled shell/exec/web tools and fixed supplied
child follows the desktop service's explicit `LOOPX_CHAT_CODEX_HOME` account
selection rather than an unrelated inherited `CODEX_HOME`. Without that Chat
selection, ordinary CLI account discovery remains unchanged. This selects an
existing native account directory; it does not copy credentials or change login.
The explicit read-only sandbox, disabled shell/exec/web tools and fixed supplied
evidence remain in force. A failed provider attempt stays in job history and
requires an explicit retry after the host problem is repaired.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ def execute(
# rules. The shared host transport delegates credentials/refresh to
# Codex; it supplies no endpoint, bearer token or account configuration.
transport = shared_chatgpt_transport({}, {})
# The desktop service pins its Chat account independently of an
# unrelated terminal's CODEX_HOME. Use that existing host selection;
# leave ordinary CLI environments and Codex-owned credentials intact.
environment = dict(os.environ)
if chat_home := environment.get("LOOPX_CHAT_CODEX_HOME"):
environment["CODEX_HOME"] = chat_home
provider = transport["model_provider"]
transport_args = ["-c", f"model_provider={json.dumps(provider)}"]
for key, value in transport["model_providers"][provider].items():
Expand Down Expand Up @@ -237,6 +243,7 @@ def execute(
stdin=prompt,
stdout=events,
stderr=errors,
env=environment,
text=True,
encoding="utf-8",
)
Expand Down
28 changes: 26 additions & 2 deletions tests/capabilities/test_trajectory_assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import json
import os
from pathlib import Path
import sys
import threading
Expand Down Expand Up @@ -64,7 +65,8 @@ def prepared(tmp_path):


def provider(
tmp_path: Path, *, delay=0, code=0, invalid=False, invalid_utf8=False
tmp_path: Path, *, delay=0, code=0, invalid=False, invalid_utf8=False,
expected_home=None,
) -> str:
script = tmp_path / "codex-fixture"
output = {
Expand All @@ -88,7 +90,7 @@ def provider(
)
script.write_text(
f"""#!{sys.executable}
import json,sys,time
import json,os,sys,time
from pathlib import Path
assert "\\u539f\\u59cb\\u76ee\\u6807" in sys.stdin.buffer.read().decode("utf-8")
options = dict(arg.split('=', 1) for index, arg in enumerate(sys.argv) if index > 0 and sys.argv[index-1] == '-c')
Expand All @@ -100,6 +102,8 @@ def provider(
assert sys.argv[sys.argv.index('--sandbox')+1] == 'read-only'
assert json.loads(options['web_search']) == 'disabled'
assert 'shell_tool' in sys.argv and 'unified_exec' in sys.argv
if {expected_home!r} is not None:
assert os.environ.get('CODEX_HOME') == {expected_home!r}
time.sleep({delay})
if {code}: sys.exit({code})
Path(sys.argv[sys.argv.index('-o')+1]).write_bytes({payload!r})
Expand All @@ -112,6 +116,26 @@ def provider(
return str(script)


@pytest.mark.parametrize("chat_home", [None, "", "selected-chat-home"])
def test_actual_child_uses_explicit_chat_account_not_unrelated_terminal(
prepared, tmp_path, monkeypatch, chat_home
):
monkeypatch.setenv("CODEX_HOME", "unrelated-terminal-home")
if chat_home is None:
monkeypatch.delenv("LOOPX_CHAT_CODEX_HOME", raising=False)
else:
monkeypatch.setenv("LOOPX_CHAT_CODEX_HOME", chat_home)
expected = "selected-chat-home" if chat_home else "unrelated-terminal-home"
result = execute(
prepared, tmp_path / "jobs",
codex=provider(tmp_path, expected_home=expected),
)
assert result["status"] == "succeeded"
# Selection belongs to this child, not the server's process or saved input.
assert os.environ["CODEX_HOME"] == "unrelated-terminal-home"
assert "CODEX_HOME" not in result["config"]


def test_actual_child_reuse_and_repair_of_derived_output(prepared, tmp_path):
first = execute(prepared, tmp_path / "jobs", codex=provider(tmp_path))
assert first["status"] == "succeeded" and first["usage"] == {
Expand Down
Loading