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
4 changes: 4 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Project-local Codex config for this repository.
#
# Keep credentials and runtime state out of this file. Codex/ChatGPT auth for
# the provider container belongs in data/codex-home, mounted at /root/.codex.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ temp/

auth.json
codex-home/
.codex/
sessions/
history.jsonl

# Track reviewed project-level Codex config without exposing Codex home state.
.codex/*
!.codex/config.toml
!.codex/rules/
.codex/rules/*
!.codex/rules/*.rules
19 changes: 18 additions & 1 deletion scripts/check_repo_hygiene.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
from pathlib import Path


ALLOWED_CODEX_PATHS = {
".codex/config.toml",
}

FORBIDDEN_STAGEABLE_PATHS = (
".env",
"data/",
".venv/",
"venv/",
".codex/",
"codex-home/",
)

Expand All @@ -28,6 +31,18 @@
}


def is_allowed_codex_path(path: str) -> bool:
if path in ALLOWED_CODEX_PATHS:
return True

rules_prefix = ".codex/rules/"
if path.startswith(rules_prefix):
rule_name = path.removeprefix(rules_prefix)
return "/" not in rule_name and rule_name.endswith(".rules")

return False


def fail(message: str) -> None:
print(f"FAIL: {message}", file=sys.stderr)
raise SystemExit(1)
Expand All @@ -53,6 +68,8 @@ def check_paths(paths: list[str]) -> None:
fail(f"forbidden path is stageable: {path}")
if any(normalized.startswith(prefix) for prefix in FORBIDDEN_STAGEABLE_PATHS if prefix.endswith("/")):
fail(f"forbidden path is stageable: {path}")
if normalized == ".codex" or (normalized.startswith(".codex/") and not is_allowed_codex_path(normalized)):
fail(f"forbidden Codex state/config path is stageable: {path}")
if Path(normalized).name in FORBIDDEN_FILENAMES:
fail(f"forbidden credential/state filename is stageable: {path}")

Expand Down
37 changes: 37 additions & 0 deletions tests/test_repo_hygiene.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest

from scripts.check_repo_hygiene import check_paths, is_allowed_codex_path


def test_codex_project_config_is_stageable():
assert is_allowed_codex_path(".codex/config.toml")
check_paths([".codex/config.toml"])


def test_codex_rules_files_are_stageable():
assert is_allowed_codex_path(".codex/rules/default.rules")
check_paths([".codex/rules/default.rules"])


@pytest.mark.parametrize(
"path",
[
".codex/auth.json",
".codex/history.jsonl",
".codex/sessions/2026/session.jsonl",
".codex/cache/state.json",
".codex/state.sqlite",
".codex/rules/nested/default.rules",
".codex/local-environment.json",
],
)
def test_codex_state_and_unknown_files_are_not_stageable(path):
assert not is_allowed_codex_path(path)
with pytest.raises(SystemExit):
check_paths([path])


@pytest.mark.parametrize("path", [".env", "data/codex-home/auth.json", "codex-home/auth.json"])
def test_existing_forbidden_paths_stay_forbidden(path):
with pytest.raises(SystemExit):
check_paths([path])
Loading