Check root Python script formatting in CI (#25165)

## Why

Python files under `scripts/` were not covered by the repository
formatting recipe or the CI formatting job, so formatting drift could
merge unnoticed.

## What

- Add a dedicated `scripts/pyproject.toml` and `scripts/uv.lock` so
root-script formatting uses a locked Ruff version.
- Extend `just fmt` to format root Python scripts and add
`fmt-scripts-check` for CI.
- Run `just fmt-scripts-check` from `.github/workflows/ci.yml`,
installing `uv` through SHA-pinned `astral-sh/setup-uv` while retaining
the `uv` `0.11.3` pin.
- Apply Ruff formatting to the root Python scripts, including
`scripts/just-shell.py`, and extend
`sdk/python/tests/test_artifact_workflow_and_binaries.py` to cover the
root formatting recipe.
- Update `AGENTS.md` so agents run `just fmt` after code changes
anywhere in the repository.

## Validation

- Extended the existing Python SDK workflow test to assert that `just
fmt` includes root Python scripts.
This commit is contained in:
Adam Perry @ OpenAI
2026-06-01 11:50:23 -07:00
committed by GitHub
Unverified
parent c3cdf3c007
commit 281b416c44
17 changed files with 183 additions and 52 deletions
+10
View File
@@ -74,5 +74,15 @@ jobs:
- name: Check root README ToC
run: python3 scripts/readme_toc.py README.md
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: just
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.3"
- name: Ruff format Python scripts (run `just fmt` to fix)
run: just fmt-scripts-check
- name: Prettier (run `pnpm run format:fix` to fix)
run: pnpm run format
+1 -1
View File
@@ -55,7 +55,7 @@ In the codex-rs folder where the rust code lives:
trivial; prefer new modules/files and keep `chatwidget.rs` focused on orchestration.
- When running Rust commands (e.g. `just fix` or `just test`) be patient with the command and never try to kill them using the PID. Rust lock can make the execution slow, this is expected.
Run `just fmt` (in `codex-rs` directory) automatically after you have finished making Rust code changes; do not ask for approval to run it. Additionally, run the tests:
Run `just fmt` (in the `codex-rs` directory) automatically after you have finished making code changes anywhere in this repository; do not ask for approval to run it. Additionally, run the tests:
1. Do not run `cargo test` directly. Use `just test` so test execution follows the repo defaults.
2. Run the test for the specific project that was changed. For example, if changes were made in `codex-rs/tui`, run `just test -p codex-tui`.
+6 -1
View File
@@ -36,11 +36,16 @@ app-server-test-client *args:
cargo build -p codex-cli
cargo run -p codex-app-server-test-client -- --codex-bin ./target/debug/codex {args}
# Format Rust and Python SDK code.
# Format Rust, Python SDK code, and Python scripts.
fmt:
cargo fmt -- --config imports_granularity=Item {stderr-null}
uv run --frozen --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python
uv run --frozen --project ../sdk/python --extra dev ruff format ../sdk/python
# Root scripts have their own locked Ruff environment.
uv run --frozen --project ../scripts ruff format ../scripts
fmt-scripts-check:
uv run --frozen --project ../scripts ruff format --check ../scripts
fix *args:
cargo clippy --fix --tests --allow-dirty {args}
+12 -4
View File
@@ -75,7 +75,9 @@ def blob_size(commit: str, path: str) -> int:
return int(run_git("cat-file", "-s", f"{commit}:{path}").strip())
def collect_changed_blobs(base: str, head: str, allowlist: set[str]) -> list[ChangedBlob]:
def collect_changed_blobs(
base: str, head: str, allowlist: set[str]
) -> list[ChangedBlob]:
blobs: list[ChangedBlob] = []
for path in get_changed_paths(base, head):
blobs.append(
@@ -137,7 +139,9 @@ def main() -> int:
parser = argparse.ArgumentParser(
description="Fail if changed blobs exceed the configured size budget."
)
parser.add_argument("--base", required=True, help="Base git revision to diff against.")
parser.add_argument(
"--base", required=True, help="Base git revision to diff against."
)
parser.add_argument("--head", required=True, help="Head git revision to inspect.")
parser.add_argument(
"--max-bytes",
@@ -156,7 +160,9 @@ def main() -> int:
allowlist = load_allowlist(args.allowlist)
blobs = collect_changed_blobs(args.base, args.head, allowlist)
violations = [
blob for blob in blobs if blob.size_bytes > args.max_bytes and not blob.is_allowlisted
blob
for blob in blobs
if blob.size_bytes > args.max_bytes and not blob.is_allowlisted
]
write_step_summary(args.max_bytes, blobs, violations)
@@ -165,7 +171,9 @@ def main() -> int:
print("No changed files were detected.")
return 0
print(f"Checked {len(blobs)} changed file(s) against the {args.max_bytes}-byte limit.")
print(
f"Checked {len(blobs)} changed file(s) against the {args.max_bytes}-byte limit."
)
for blob in blobs:
status = "allowlisted" if blob.is_allowlisted else "ok"
if blob in violations:
+7 -2
View File
@@ -99,7 +99,9 @@ def resolve_zstd_command(
def write_zip_archive(package_dir: Path, archive_path: Path) -> None:
with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
with zipfile.ZipFile(
archive_path, "w", compression=zipfile.ZIP_DEFLATED
) as archive:
for path in package_entries(package_dir):
relative_path = path.relative_to(package_dir)
if path.is_dir():
@@ -109,4 +111,7 @@ def write_zip_archive(package_dir: Path, archive_path: Path) -> None:
def package_entries(package_dir: Path) -> list[Path]:
return sorted(package_dir.rglob("*"), key=lambda path: path.relative_to(package_dir).as_posix())
return sorted(
package_dir.rglob("*"),
key=lambda path: path.relative_to(package_dir).as_posix(),
)
+4 -3
View File
@@ -44,8 +44,7 @@ def build_source_binaries(
variant,
build_entrypoint=entrypoint_bin is None,
build_bwrap=spec.is_linux and bwrap_bin is None,
build_codex_command_runner=spec.is_windows
and codex_command_runner_bin is None,
build_codex_command_runner=spec.is_windows and codex_command_runner_bin is None,
build_codex_windows_sandbox_setup=spec.is_windows
and codex_windows_sandbox_setup_bin is None,
)
@@ -138,7 +137,9 @@ def validate_prebuilt_resource_inputs(
)
def resolve_output_path(explicit_path: Path | None, default_path: Path | None) -> Path | None:
def resolve_output_path(
explicit_path: Path | None, default_path: Path | None
) -> Path | None:
if explicit_path is not None:
return explicit_path.resolve()
+5 -5
View File
@@ -44,8 +44,7 @@ def parse_args() -> argparse.Namespace:
type=Path,
default=argparse.SUPPRESS,
help=(
"Output directory to create as the package root. Defaults to a new "
"temporary directory."
"Output directory to create as the package root. Defaults to a new temporary directory."
),
)
parser.add_argument(
@@ -72,8 +71,7 @@ def parse_args() -> argparse.Namespace:
"--cargo-profile",
default="dev-small",
help=(
"Cargo profile for source-built package artifacts. Use release for "
"release packages."
"Cargo profile for source-built package artifacts. Use release for release packages."
),
)
parser.add_argument(
@@ -169,7 +167,9 @@ def main() -> int:
)
prepare_package_dir(package_dir, force=args.force)
build_package_dir(package_dir, version, variant, spec, inputs)
validate_package_dir(package_dir, variant, spec, include_zsh=inputs.zsh_bin is not None)
validate_package_dir(
package_dir, variant, spec, include_zsh=inputs.zsh_bin is not None
)
for archive_output in args.archive_output:
archive_path = archive_output.resolve()
+3 -1
View File
@@ -17,7 +17,9 @@ LAYOUT_VERSION = 1
def prepare_package_dir(package_dir: Path, *, force: bool) -> None:
if package_dir.exists():
if not package_dir.is_dir():
raise RuntimeError(f"Package output exists and is not a directory: {package_dir}")
raise RuntimeError(
f"Package output exists and is not a directory: {package_dir}"
)
if any(package_dir.iterdir()):
if not force:
raise RuntimeError(
+6 -2
View File
@@ -27,7 +27,9 @@ class SourceBinariesForTargetTest(unittest.TestCase):
[],
)
def test_linux_package_with_prebuilt_entrypoint_and_bwrap_builds_nothing(self) -> None:
def test_linux_package_with_prebuilt_entrypoint_and_bwrap_builds_nothing(
self,
) -> None:
self.assertEqual(
source_binaries_for_target(
TARGET_SPECS["x86_64-unknown-linux-musl"],
@@ -40,7 +42,9 @@ class SourceBinariesForTargetTest(unittest.TestCase):
[],
)
def test_windows_package_with_prebuilt_entrypoint_and_helpers_builds_nothing(self) -> None:
def test_windows_package_with_prebuilt_entrypoint_and_helpers_builds_nothing(
self,
) -> None:
self.assertEqual(
source_binaries_for_target(
TARGET_SPECS["x86_64-pc-windows-msvc"],
+18 -11
View File
@@ -43,8 +43,7 @@ def resolve_codex_v8_cargo_env(
return {}
if archive_override or binding_override:
raise RuntimeError(
"Cargo package builds need RUSTY_V8_ARCHIVE and "
"RUSTY_V8_SRC_BINDING_PATH set together."
"Cargo package builds need RUSTY_V8_ARCHIVE and RUSTY_V8_SRC_BINDING_PATH set together."
)
artifacts = fetch_codex_v8_artifacts(spec, cache_root=cache_root)
@@ -61,12 +60,13 @@ def fetch_codex_v8_artifacts(
cache_root: Path | None = None,
) -> RustyV8ArtifactPair:
if spec.is_windows:
raise RuntimeError(f"No Codex-built V8 release artifacts for target: {spec.target}")
raise RuntimeError(
f"No Codex-built V8 release artifacts for target: {spec.target}"
)
version = version or resolved_v8_crate_version()
release_url = (
"https://github.com/openai/codex/releases/download/"
f"rusty-v8-v{version}"
f"https://github.com/openai/codex/releases/download/rusty-v8-v{version}"
)
target = spec.target
cache_dir = (cache_root or default_cache_root()) / f"rusty-v8-{version}-{target}"
@@ -98,7 +98,9 @@ def resolved_v8_crate_version() -> str:
}
)
if len(versions) != 1:
raise RuntimeError(f"Expected exactly one resolved v8 version, found: {versions}")
raise RuntimeError(
f"Expected exactly one resolved v8 version, found: {versions}"
)
return versions[0]
@@ -111,18 +113,21 @@ def load_checksums(checksums_path: Path, artifact_names: set[str]) -> dict[str,
lines = checksums_path.read_text(encoding="utf-8").splitlines()
if len(lines) != len(artifact_names):
raise RuntimeError(
f"Expected {len(artifact_names)} V8 checksums in {checksums_path}, "
f"found {len(lines)}."
f"Expected {len(artifact_names)} V8 checksums in {checksums_path}, found {len(lines)}."
)
for line in lines:
parts = line.split(maxsplit=1)
if len(parts) != 2:
raise RuntimeError(f"Invalid V8 checksum line in {checksums_path}: {line!r}")
raise RuntimeError(
f"Invalid V8 checksum line in {checksums_path}: {line!r}"
)
digest, artifact_name = parts[0], parts[1].strip()
if len(digest) != 64 or any(char not in "0123456789abcdef" for char in digest):
raise RuntimeError(f"Invalid V8 checksum digest in {checksums_path}: {digest}")
raise RuntimeError(
f"Invalid V8 checksum digest in {checksums_path}: {digest}"
)
if artifact_name not in artifact_names:
raise RuntimeError(
f"Unexpected V8 checksum artifact in {checksums_path}: {artifact_name}"
@@ -146,7 +151,9 @@ def ensure_valid_artifact(artifact: Path, checksum: str, url: str) -> None:
return
artifact.unlink(missing_ok=True)
raise RuntimeError(f"Codex-built V8 artifact {artifact} failed checksum validation.")
raise RuntimeError(
f"Codex-built V8 artifact {artifact} failed checksum validation."
)
def has_checksum(path: Path, expected: str) -> bool:
+1 -1
View File
@@ -16,7 +16,7 @@ import sys
ARGS_TOKEN = "{args}"
STDERR_NULL_TOKEN = "{stderr-null}"
POWERSHELL_ARGS = "@($args | Select-Object -Skip 1)"
POWERSHELL_STDERR_NULL = '2>$null; exit $LASTEXITCODE'
POWERSHELL_STDERR_NULL = "2>$null; exit $LASTEXITCODE"
SH_ARGS = '"$@"'
SH_STDERR_NULL = "2>/dev/null"
+17 -4
View File
@@ -44,13 +44,23 @@ def _event_response_done() -> dict[str, Any]:
def _event_response_completed(response_id: str) -> dict[str, Any]:
return {"type": "response.completed", "response": {"id": response_id, "usage": _default_usage()}}
return {
"type": "response.completed",
"response": {"id": response_id, "usage": _default_usage()},
}
def _event_function_call(call_id: str, name: str, arguments_json: str) -> dict[str, Any]:
def _event_function_call(
call_id: str, name: str, arguments_json: str
) -> dict[str, Any]:
return {
"type": "response.output_item.done",
"item": {"type": "function_call", "call_id": call_id, "name": name, "arguments": arguments_json},
"item": {
"type": "function_call",
"call_id": call_id,
"name": name,
"arguments": arguments_json,
},
}
@@ -75,6 +85,7 @@ def _print_request(prefix: str, payload: Any) -> None:
sys.stdout.write(f"{prefix} {_utc_iso()}\n{pretty}\n")
sys.stdout.flush()
async def _handle_connection(
websocket: Any,
*,
@@ -91,7 +102,9 @@ async def _handle_connection(
path_no_qs = path.split("?", 1)[0] if path != "(unknown)" else path
if path_no_qs != "(unknown)" and path_no_qs != expected_path:
sys.stdout.write(f"[conn] {_utc_iso()} rejecting unexpected path (expected {expected_path})\n")
sys.stdout.write(
f"[conn] {_utc_iso()} rejecting unexpected path (expected {expected_path})\n"
)
sys.stdout.flush()
await websocket.close(code=1008, reason="unexpected websocket path")
return
+9
View File
@@ -0,0 +1,9 @@
[project]
name = "codex-scripts"
version = "0.0.0"
requires-python = ">=3.10"
dependencies = ["ruff>=0.15.8"]
[tool.uv]
exclude-newer = "7 days"
index-strategy = "first-index"
+2 -2
View File
@@ -88,7 +88,7 @@ def check_or_fix(readme_path: Path, fix: bool) -> int:
current_block = lines[begin_idx + 1 : end_idx]
current = [l for l in current_block if l.lstrip().startswith("- [")]
# generate expected ToC from content without current ToC
toc_content = lines[:begin_idx] + lines[end_idx+1:]
toc_content = lines[:begin_idx] + lines[end_idx + 1 :]
expected = generate_toc_lines("\n".join(toc_content))
if current == expected:
return 0
@@ -109,7 +109,7 @@ def check_or_fix(readme_path: Path, fix: bool) -> int:
return 1
# rebuild file with updated ToC
prefix = lines[: begin_idx + 1]
suffix = lines[end_idx+1:]
suffix = lines[end_idx + 1 :]
new_lines = prefix + [""] + expected + [""] + suffix
readme_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8")
print(f"Updated ToC in {readme_path}.")
+33 -11
View File
@@ -37,7 +37,9 @@ _SPEC.loader.exec_module(_BUILD_MODULE)
PACKAGE_NATIVE_COMPONENTS = getattr(_BUILD_MODULE, "PACKAGE_NATIVE_COMPONENTS", {})
PACKAGE_EXPANSIONS = getattr(_BUILD_MODULE, "PACKAGE_EXPANSIONS", {})
CODEX_PLATFORM_PACKAGES = getattr(_BUILD_MODULE, "CODEX_PLATFORM_PACKAGES", {})
CODEX_PACKAGE_COMPONENT = getattr(_BUILD_MODULE, "CODEX_PACKAGE_COMPONENT", "codex-package")
CODEX_PACKAGE_COMPONENT = getattr(
_BUILD_MODULE, "CODEX_PACKAGE_COMPONENT", "codex-package"
)
@dataclass(frozen=True)
@@ -159,7 +161,9 @@ def resolve_release_workflow(version: str) -> dict:
)
workflow = json.loads(stdout or "null")
if not workflow:
raise RuntimeError(f"Unable to find rust-release workflow for version {version}.")
raise RuntimeError(
f"Unable to find rust-release workflow for version {version}."
)
return workflow
@@ -386,13 +390,17 @@ def install_single_binary(
component: BinaryComponent,
) -> Path:
artifact_subdir = artifact_dir_for_target(artifacts_dir, target)
archive_path = binary_archive_path(artifact_subdir, component.artifact_prefix, target)
archive_path = binary_archive_path(
artifact_subdir, component.artifact_prefix, target
)
dest_dir = vendor_dir / target / component.dest_dir
dest_dir.mkdir(parents=True, exist_ok=True)
binary_name = (
f"{component.binary_basename}.exe" if "windows" in target else component.binary_basename
f"{component.binary_basename}.exe"
if "windows" in target
else component.binary_basename
)
dest = dest_dir / binary_name
dest.unlink(missing_ok=True)
@@ -405,14 +413,18 @@ def install_single_binary(
def binary_archive_path(artifact_dir: Path, artifact_prefix: str, target: str) -> Path:
archive_names = [archive_name_for_target(artifact_prefix, target)]
if artifact_dir.name == f"{target}-unsigned":
archive_names.append(archive_name_for_target(artifact_prefix, f"{target}-unsigned"))
archive_names.append(
archive_name_for_target(artifact_prefix, f"{target}-unsigned")
)
for archive_name in archive_names:
archive_path = artifact_dir / archive_name
if archive_path.exists():
return archive_path
raise FileNotFoundError(f"Expected artifact not found: {artifact_dir / archive_names[0]}")
raise FileNotFoundError(
f"Expected artifact not found: {artifact_dir / archive_names[0]}"
)
def archive_name_for_target(artifact_prefix: str, target: str) -> str:
@@ -434,7 +446,9 @@ def extract_zstd_archive(archive_path: Path, dest: Path) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
output_path = archive_path.parent / dest.name
subprocess.check_call(["zstd", "-f", "-d", str(archive_path), "-o", str(output_path)])
subprocess.check_call(
["zstd", "-f", "-d", str(archive_path), "-o", str(output_path)]
)
shutil.move(str(output_path), dest)
@@ -497,7 +511,9 @@ def main() -> int:
)
print(f"Caching downloaded artifacts in {artifacts_temp_root}", flush=True)
for components in native_component_sets:
vendor_temp_root = Path(tempfile.mkdtemp(prefix="npm-native-", dir=runner_temp))
vendor_temp_root = Path(
tempfile.mkdtemp(prefix="npm-native-", dir=runner_temp)
)
vendor_temp_roots.append(vendor_temp_root)
print(
"Installing native components "
@@ -517,8 +533,12 @@ def main() -> int:
print(f"should `git checkout {resolved_head_sha}`", flush=True)
for package in packages:
staging_dir = Path(tempfile.mkdtemp(prefix=f"npm-stage-{package}-", dir=runner_temp))
pack_output = output_dir / tarball_name_for_package(package, args.release_version)
staging_dir = Path(
tempfile.mkdtemp(prefix=f"npm-stage-{package}-", dir=runner_temp)
)
pack_output = output_dir / tarball_name_for_package(
package, args.release_version
)
print(f"Staging {package} in {staging_dir}", flush=True)
cmd = [
@@ -533,7 +553,9 @@ def main() -> int:
str(pack_output),
]
vendor_src = vendor_src_by_components.get(native_components_for_package(package))
vendor_src = vendor_src_by_components.get(
native_components_for_package(package)
)
if vendor_src is not None:
cmd.extend(["--vendor-src", str(vendor_src)])
+43
View File
@@ -0,0 +1,43 @@
version = 1
revision = 3
requires-python = ">=3.10"
[options]
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer-span = "P7D"
[[package]]
name = "codex-scripts"
version = "0.0.0"
source = { virtual = "." }
dependencies = [
{ name = "ruff" },
]
[package.metadata]
requires-dist = [{ name = "ruff", specifier = ">=0.15.8" }]
[[package]]
name = "ruff"
version = "0.15.13"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" },
{ url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" },
{ url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" },
{ url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" },
{ url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" },
{ url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" },
{ url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" },
{ url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" },
{ url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" },
{ url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" },
{ url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" },
{ url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" },
{ url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" },
{ url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" },
{ url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" },
{ url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" },
{ url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" },
]
@@ -68,8 +68,8 @@ def test_generation_has_single_maintenance_entrypoint_script() -> None:
assert scripts == ["update_sdk_artifacts.py"]
def test_root_fmt_recipe_formats_rust_and_python_sdk() -> None:
"""The repo fmt command should work from Rust and Python SDK directories."""
def test_root_fmt_recipe_formats_rust_python_sdk_and_scripts() -> None:
"""The repo fmt command should format Rust, the Python SDK, and scripts."""
justfile = ROOT.parents[1] / "justfile"
lines = justfile.read_text().splitlines()
fmt_index = lines.index("fmt:")
@@ -88,16 +88,18 @@ def test_root_fmt_recipe_formats_rust_and_python_sdk() -> None:
}
expected = {
"working_directory": 'set working-directory := "codex-rs"',
"previous_comment": "# Format Rust and Python SDK code.",
"previous_comment": "# Format Rust, Python SDK code, and Python scripts.",
"commands": [
"cargo fmt -- --config imports_granularity=Item {stderr-null}",
"uv run --frozen --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python",
"uv run --frozen --project ../sdk/python --extra dev ruff format ../sdk/python",
"# Root scripts have their own locked Ruff environment.",
"uv run --frozen --project ../scripts ruff format ../scripts",
],
}
assert actual == expected, (
"The root `just fmt` recipe must run Rust fmt and Python SDK Ruff. "
"The root `just fmt` recipe must run Rust fmt and Ruff for Python SDK code and scripts. "
"Fix the `fmt` recipe in `justfile`, then run `just fmt`.\n"
f"Expected: {json.dumps(expected, indent=2)}\n"
f"Actual: {json.dumps(actual, indent=2)}"