项目文件夹

文件
Ali Khokhar b323b45434 Isolate nested Codex launches from parent task context (#1055)
## Problem\n\nNested Codex launches inherited the parent task's thread,
shell, permission, and origin identity. This could attach a new client
or compatibility probe to the parent task, hang startup, or decode probe
output with the wrong Windows locale.\n\n## Changes\n\n| Before | After
|\n| --- | --- |\n| cc-codex forwarded parent-only Codex task context. |
cc-codex removes parent-only task context while preserving user
configuration such as CODEX_HOME. |\n| The installed-Codex probe
inherited the parent task and user Codex home. | The probe runs with a
standalone task identity and isolated Codex home. |\n| The probe decoded
output with the platform default encoding. | The probe decodes output as
UTF-8 with safe replacement. |

<!-- greptile_comment -->

<details open><summary><h3>Greptile Summary</h3></summary>

This PR isolates nested Codex launches from parent task identity. The
main changes are:

- Strips Codex thread, shell, permission, and origin context from
launched child environments.
- Preserves `CODEX_HOME` as durable user configuration.
- Runs the installed Codex catalog probe with an isolated home and
cleaned task context.
- Decodes probe output as UTF-8 with replacement.
- Updates architecture notes and bumps the package version to `3.5.3`.
</details>

<h3>Confidence Score: 5/5</h3>

This looks safe to merge.

No blocking issues found in the changed code.

No files need attention.

<details><summary><h3><a href="https://www.greptile.com/trex"><img
alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="20" align="absmiddle"></a> T-Rex Logs</h3></summary>

**What T-Rex did**
- Logged the proof with exact commands, the working directory,
timestamps, and test and lint summaries, including exit codes.
- The first pytest capture showed tests passed but the shell wrapper
exited with code 2 because /bin/sh does not support ${PIPESTATUS\[0\]}.
- Re-ran the proof under bash, and the run exited with code 0.
- Artifacts containing the three proof logs were created for review.

<a
href="https://app.greptile.com/trex/runs/14092911/artifacts"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=4"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"><img
alt="View all artifacts"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"></picture></a>

<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>
</details>

<details open><summary><h3>Important Files Changed</h3></summary>

| Filename | Overview |
|----------|----------|
| src/free_claude_code/cli/launchers/codex.py | Strips parent-only Codex
context variables from the child launcher environment while keeping
`CODEX_HOME`. |
| tests/cli/test_entrypoints.py | Adds launcher coverage for stripped
Codex task variables and preserved `CODEX_HOME`. |
| tests/cli/test_codex_model_catalog.py | Runs the installed Codex
catalog probe with a cleaned environment, isolated home, and UTF-8
replacement decoding. |
| ARCHITECTURE.md | Documents the Codex launcher context-isolation
behavior. |
| pyproject.toml | Bumps the project version to `3.5.3`. |
| uv.lock | Updates the locked editable package version to `3.5.3`. |

</details>

<sub>Reviews (3): Last reviewed commit: ["Document Codex home
ownership"](https://github.com/alishahryar1/free-claude-code/commit/2140315b1f33c4162e74ca8f816228f50c38c0a1)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=43513448)</sub>

<!-- /greptile_comment -->
2026-07-11 06:59:05 -07:00

185 行
5.2 KiB
Python

import json
import os
import shutil
import subprocess
from collections.abc import Mapping
from pathlib import Path
from typing import Any, cast
import pytest
from free_claude_code.cli.launchers.codex_model_catalog import (
build_codex_model_catalog,
write_codex_model_catalog,
)
def _models_payload(*model_ids: str) -> dict[str, Any]:
return {
"data": [
{
"id": model_id,
"display_name": model_id.replace("anthropic/", ""),
}
for model_id in model_ids
]
}
def _catalog_models(catalog: Mapping[str, Any]) -> list[Mapping[str, Any]]:
models = catalog["models"]
assert isinstance(models, list)
catalog_models: list[Mapping[str, Any]] = []
for model in models:
assert isinstance(model, Mapping)
catalog_models.append(cast(Mapping[str, Any], model))
return catalog_models
def _slugs(catalog: Mapping[str, Any]) -> list[str]:
slugs: list[str] = []
for model in _catalog_models(catalog):
slug = model["slug"]
assert isinstance(slug, str)
slugs.append(slug)
return slugs
def test_codex_catalog_converts_configured_and_cached_models_to_direct_slugs() -> None:
catalog = build_codex_model_catalog(
_models_payload(
"anthropic/nvidia_nim/nvidia/nemotron-3-super",
"claude-3-freecc-no-thinking/nvidia_nim/nvidia/nemotron-3-super",
"anthropic/open_router/meta-llama/llama-3.3-70b",
"claude-3-freecc-no-thinking/open_router/meta-llama/llama-3.3-70b",
)
)
assert _slugs(catalog) == [
"nvidia_nim/nvidia/nemotron-3-super",
"open_router/meta-llama/llama-3.3-70b",
]
model = _catalog_models(catalog)[0]
assert {
"slug",
"display_name",
"description",
"default_reasoning_level",
"supported_reasoning_levels",
"shell_type",
"visibility",
"supported_in_api",
"priority",
"additional_speed_tiers",
"service_tiers",
} <= set(model)
def test_codex_catalog_excludes_claude_compatibility_model_ids() -> None:
catalog = build_codex_model_catalog(
_models_payload(
"claude-opus-4-20250514",
"claude-3-haiku-20240307",
"anthropic/nvidia_nim/provider-model",
)
)
assert _slugs(catalog) == ["nvidia_nim/provider-model"]
def test_codex_catalog_skips_no_thinking_duplicate_when_normal_slug_exists() -> None:
catalog = build_codex_model_catalog(
_models_payload(
"claude-3-freecc-no-thinking/nvidia_nim/provider-model",
"anthropic/nvidia_nim/provider-model",
)
)
assert _slugs(catalog) == ["nvidia_nim/provider-model"]
def test_codex_catalog_preserves_no_thinking_only_entries_for_routing() -> None:
catalog = build_codex_model_catalog(
_models_payload("claude-3-freecc-no-thinking/open_router/plain-model")
)
assert _slugs(catalog) == ["claude-3-freecc-no-thinking/open_router/plain-model"]
def test_codex_catalog_ordering_and_priorities_are_deterministic() -> None:
catalog = build_codex_model_catalog(
_models_payload(
"anthropic/gemini/models/gemini-test",
"anthropic/nvidia_nim/nvidia/test",
"anthropic/gemini/models/gemini-test",
"anthropic/open_router/provider/test",
)
)
models = _catalog_models(catalog)
assert _slugs(catalog) == [
"gemini/models/gemini-test",
"nvidia_nim/nvidia/test",
"open_router/provider/test",
]
assert [model["priority"] for model in models] == [0, 1, 2]
def test_codex_catalog_accepts_future_direct_provider_slugs() -> None:
catalog = build_codex_model_catalog(
_models_payload(
"nvidia_nim/provider-model",
"anthropic/open_router/provider-model",
)
)
assert _slugs(catalog) == [
"nvidia_nim/provider-model",
"open_router/provider-model",
]
def test_generated_catalog_schema_is_accepted_by_installed_codex(
tmp_path: Path,
) -> None:
codex_binary = shutil.which("codex")
if codex_binary is None:
pytest.skip("Codex CLI is not installed")
catalog_path = tmp_path / "codex-model-catalog.json"
write_codex_model_catalog(
catalog_path,
build_codex_model_catalog(_models_payload("anthropic/nvidia_nim/test-model")),
)
codex_home = tmp_path / "codex-home"
codex_home.mkdir()
codex_env = os.environ.copy()
for key in (
"CODEX_THREAD_ID",
"CODEX_INTERNAL_ORIGINATOR_OVERRIDE",
"CODEX_SHELL",
"CODEX_PERMISSION_PROFILE",
):
codex_env.pop(key, None)
codex_env["CODEX_HOME"] = str(codex_home)
result = subprocess.run(
[
codex_binary,
"debug",
"models",
"-c",
f"model_catalog_json={json.dumps(str(catalog_path))}",
],
capture_output=True,
check=False,
encoding="utf-8",
env=codex_env,
errors="replace",
text=True,
timeout=10,
)
assert result.returncode == 0, result.stderr
assert "nvidia_nim/test-model" in result.stdout