Skip to the content.

Антон:

You are an INDEPENDENT senior code reviewer from a DIFFERENT vendor than the author. The author (Codex) wrote the two-repository change below. Review ONLY the supplied diffs; do not use tools or read other files. Be concrete and skeptical.

Task: local Windows Claude/Codex shared-file write protection. Required properties: two concurrent writers of one absolute file produce one owner and BUSY rc=7 naming the holder; foreign release and delayed old-turn release are harmless; old-schema migration is race-safe and token-aware Stop can release a migrated legacy row; real Codex shell/apply_patch hook wiring is tested; counters are attributable; child-script, relative-path, Windows-only, per-checkout and local-only boundaries are honest.

Check correctness, security bypasses, caller breakage, races, and whether tests really kill their named mutations. Prioritize real MED/HIGH defects; do not block on style.

CRITICAL OUTPUT CONTRACT: the VERY LAST line of your reply must be exactly ===VERDICT=== where is one bare word: APPROVE (no blocking issues) or REQUEST_CHANGES (one or more MED/HIGH issues). No backticks, no bold, no brackets, nothing else on that line. State it exactly once, on that final line only.

Format before the final line: SUMMARY: FINDINGS:

— SCRIPTS DIFF START —diff –git a/_test_codex_shared_workspace_safety.py b/_test_codex_shared_workspace_safety.py deleted file mode 100644 index 4d7d597e5..000000000 — a/_test_codex_shared_workspace_safety.py +++ /dev/null @@ -1,273 +0,0 @@ -# -- coding: utf-8 -- -“"”Integration acceptance for Claude/Codex shared-workspace safety. - -Purpose: prove the active Codex registry has one local shelf, both harness configs -route writes through the same guard, canonical apply_patch is really parsed, and a -Stop event releases the turn lease. Input: live configs plus an isolated temp DB. -Output: PASS/FAIL and evidence; never edits a real workspace file. -Caller: /tt after shared-workspace changes. Rail: local Python + codex debug, 0 LLM. -updated: 2026-09-11 -“”” -from future import annotations - -import json -import os -import re -import shutil -import subprocess -import sys -import tempfile -import time - -HOME = os.path.expanduser(“~”) -SCRIPTS = os.path.join(HOME, “.claude”, “scripts”) -HOOKS = os.path.join(HOME, “.claude”, “hooks”) -GUARD = os.path.join(HOOKS, “workspace_write_guard.py”) -CONSTITUTION = os.path.join(HOOKS, “constitution_guard.py”) -ORPHAN = os.path.join(HOOKS, “orphan_check_hook.py”) -TURNSTATE = os.path.join(HOOKS, “turnstate_hook.py”) -CODEX_HOOKS = os.path.join(HOME, “.codex”, “hooks.json”) -CLAUDE_SETTINGS = os.path.join(HOME, “.claude”, “settings.json”) -R1 = os.path.join(HOME, “.agents”, “skills”) -RESULTS = [] - - -def check(name, ok, detail=””):

-Purpose: prove that eight Claude/Codex sessions racing for one path produce exactly -one owner, that parent/child paths conflict, and that an expired owner is reaped. -Input/output: temporary local SQLite DB and JSONL counter; prints checks, rc 0/1. -Caller: /tt for Codex Shared Workspace Safety and future regression runs. -Rail: local Python stdlib, 0 LLM, 0 network. -Test: this file; its naive read-then-write mutant must produce multiple winners. -updated: 2026-09-11 +Purpose: prove atomic local contention and fenced release. Two real CLI writers of +one absolute file must split rc=0/7, BUSY must name the winner, and neither a foreign +release nor a delayed old epoch may remove a newer lease. Input/output: isolated +SQLite DB + JSONL counter; prints checks, rc 0/1. Caller: /tt for Codex Shared +Workspace Safety. Rail: local Python stdlib, 0 LLM, 0 network. + +KILL-LIST (source mutation -> case that must fail): +- replace BEGIN IMMEDIATE acquisition with read-then-write ->

import json +import importlib.util +import inspect import os import shutil +import sqlite3 import subprocess import sys import tempfile @@ -39,19 +58,38 @@ def _wait(ts): time.sleep(min(0.02, left / 2))

-def _child(mode, root, start, holder, target): +def _load_candidate(path, label):

-def _race(mode, root, target): +def _race(mode, root, target, candidate=””): start = time.time() + LEAD_SEC ps = [subprocess.Popen( [sys.executable, os.path.abspath(file), “–child”, mode, root,

def main():

     if lease is not None: +            acquire_mutant = _acquire_mutant( +                lease.__file__, os.path.join(root, "workspace_write_lease_acquire_mutant.py")) +            acquire_mutant_root = os.path.join(root, "acquire-mutant") +            os.makedirs(acquire_mutant_root, exist_ok=True) +            acquire_mutant_wins, acquire_mutant_errors = _race( +                "candidate", acquire_mutant_root, +                os.path.join(acquire_mutant_root, "same.txt"), acquire_mutant) +            check("mutation: acquire без BEGIN IMMEDIATE обязана дать >1 winner", +                  acquire_mutant_wins > 1 and not acquire_mutant_errors, +                  "wins=%d errors=%s" % +                  (acquire_mutant_wins, acquire_mutant_errors))
         try:
             os.remove(os.environ["WORKSPACE_WRITE_LEASE_DB"])
         except OSError:
             pass
         print("== GREEN TARGET: настоящая параллельная гонка ==")
         wins, errors = _race("atomic", root, target) -            check("8 процессов -> ровно один владелец", wins == 1, +            check("8 процессов -> один владелец, 7 доказанных losers", +                  wins == 1 and not errors,
               "wins=%d errors=%s" % (wins, errors))
         lease.release_session("A")
         lease.release_session("B") -            first = lease.acquire_many([target], "old", "claude", 1, now=100.0) -            second = lease.acquire_many([target], "new", "codex", 60, now=102.0) -            check("TTL/reaper освобождает мёртвую лизу", first.ok and second.ok) -            check("после перехвата владелец новый", -                  lease.owner_of(target, now=102.0).get("session") == "new") -            check("чужой release не снимает", -                  lease.release_session("old") == 0 and -                  lease.owner_of(target, now=102.0).get("session") == "new") -            check("свой release снимает", lease.release_session("new") == 1 and -                  lease.owner_of(target, now=102.0) is None) +            if epoch_supported: +                cli_epoch_ok, cli_epoch_steps = _cli_epoch_lifecycle( +                    lease.__file__, os.path.join(root, "cli-epoch-live"), +                    os.path.join(root, "cli-epoch-target.txt")) +                check("CLI same-session epoch acquire/release fenced", +                      cli_epoch_ok, cli_epoch_steps) +                cli_mutation_results = [] +                for operation in ("acquire", "release"): +                    cli_mutant = _cli_epoch_mutant( +                        lease.__file__, os.path.join( +                            root, "workspace_write_lease_cli_%s_mutant.py" % operation), +                        operation) +                    survived, steps = _cli_epoch_lifecycle( +                        cli_mutant, os.path.join(root, "cli-%s-mutant" % operation), +                        os.path.join(root, "cli-%s-target.txt" % operation)) +                    cli_mutation_results.append((operation, survived, steps)) +                check("mutation: CLI обязан передавать epoch в acquire/release", +                      all(not survived for _operation, survived, _steps +                          in cli_mutation_results), +                      [(operation, survived, [step["rc"] for step in steps]) +                       for operation, survived, steps in cli_mutation_results]) +                check("старая DB мигрирует и token-aware Stop снимает legacy row", +                      _old_schema_migrates( +                          lease, os.path.join(root, "legacy-handoff-live"))) +                handoff_mutant_file = _legacy_handoff_mutant( +                    lease.__file__, os.path.join( +                        root, "workspace_write_lease_handoff_mutant.py")) +                handoff_mutant = _load_candidate(handoff_mutant_file, "handoff") +                check("mutation: legacy migration handoff обязана стать красной", +                      not _old_schema_migrates( +                          handoff_mutant, os.path.join(root, "legacy-handoff-mutant"))) +                migration_errors = [] +                for attempt in range(3): +                    migration_errors.extend(_migration_wave( +                        lease, lease.__file__, os.path.join(root, "migration-live-%d" % attempt))) +                check("3x24 конкурентных first-open безопасно мигрируют старую DB", +                      not migration_errors, migration_errors[:3]) +                mutant_file = _migration_mutant( +                    lease.__file__, os.path.join(root, "workspace_write_lease_mutant.py")) +                mutant_errors = _migration_wave( +                    lease, mutant_file, os.path.join(root, "migration-mutant")) +                check("mutation: migration без lock/recheck обязана стать красной", +                      bool(mutant_errors) and any( +                          "duplicate column name: epoch" in error for error in mutant_errors), +                      mutant_errors[:3]) +                original_db = os.environ["WORKSPACE_WRITE_LEASE_DB"] +                owner_mutant_file = _owner_identity_mutant( +                    lease.__file__, os.path.join(root, "workspace_write_lease_owner_mutant.py")) +                os.environ["WORKSPACE_WRITE_LEASE_DB"] = os.path.join( +                    root, "owner-mutant.sqlite3") +                try: +                    owner_mutant = _load_candidate(owner_mutant_file, "owner") +                    mutant_old = owner_mutant.acquire_many( +                        [target], "same-session", "codex", 60, +                        now=90.0, epoch="turn-live-old") +                    mutant_new = owner_mutant.acquire_many( +                        [target], "same-session", "codex", 60, +                        now=91.0, epoch="turn-live-new") +                    check("mutation: session-only owner обязана стать красной", +                          mutant_old.ok and mutant_new.ok, +                          "old=%s new=%s" % (mutant_old.ok, mutant_new.ok)) +                finally: +                    os.environ["WORKSPACE_WRITE_LEASE_DB"] = original_db +                lease.release_all_for_tests() +                alive_old = lease.acquire_many([target], "same-session", "codex", 60, +                                               now=90.0, epoch="turn-live-old") +                blocked_new = lease.acquire_many([target], "same-session", "codex", 60, +                                                 now=91.0, epoch="turn-live-new") +                check("живая старая эпоха блокирует новую эпоху того же session", +                      alive_old.ok and not blocked_new.ok and +                      bool(blocked_new.conflicts) and +                      blocked_new.conflicts[0].get("epoch") == "turn-live-old", +                      blocked_new.conflicts) +                lease.release_session("same-session", epoch="turn-live-old") +                admitted_new = lease.acquire_many([target], "same-session", "codex", 60, +                                                 now=91.0, epoch="turn-live-new") +                check("новая эпоха проходит после точного release старой", +                      admitted_new.ok) +                lease.release_session("same-session", epoch="turn-live-new") +                first = lease.acquire_many([target], "same-session", "codex", 1, +                                           now=100.0, epoch="turn-old") +                second = lease.acquire_many([target], "same-session", "codex", 60, +                                            now=102.0, epoch="turn-new") +                owner = lease.owner_of(target, now=102.0) +                check("TTL/reaper даёт тому же session новую эпоху", +                      first.ok and second.ok and owner and +                      owner.get("epoch") == "turn-new", owner) +                foreign = lease.release_session("intruder", epoch="turn-new") +                check("чужой release не снимает новую эпоху", +                      foreign == 0 and +                      lease.owner_of(target, now=102.0).get("epoch") == "turn-new") +                stale = lease.release_session("same-session", epoch="turn-old") +                check("поздний release старой эпохи не снимает новую", +                      stale == 0 and +                      lease.owner_of(target, now=102.0).get("epoch") == "turn-new") +                check("точный release новой эпохи снимает", +                      lease.release_session("same-session", epoch="turn-new") == 1 and +                      lease.owner_of(target, now=102.0) is None) + +                check("mutation: unfenced release обязан стать красным", +                      not _late_release_invariant(lease, target, mutant=True)) +                release_mutant_file = _release_mutant( +                    lease.__file__, os.path.join(root, "workspace_write_lease_release_mutant.py")) +                os.environ["WORKSPACE_WRITE_LEASE_DB"] = os.path.join( +                    root, "release-mutant.sqlite3") +                try: +                    release_mutant = _load_candidate(release_mutant_file, "release") +                    check("mutation-copy: unfenced release обязана стать красной", +                          not _late_release_invariant( +                              release_mutant, target, mutant=False)) +                finally: +                    os.environ["WORKSPACE_WRITE_LEASE_DB"] = original_db +                check("fenced release проходит тот же detector", +                      _late_release_invariant(lease, target, mutant=False)) + +                lease.release_all_for_tests() +                tokened = lease.acquire_many([target], "legacy", "hook-caller", 60, +                                             epoch="tokened-turn") +                broad = lease.release_session("legacy") +                check("legacy release без token не снимает fenced lease", +                      tokened.ok and broad == 0 and +                      lease.owner_of(target).get("epoch") == "tokened-turn") +                lease.release_session("legacy", epoch="tokened-turn") +                legacy = lease.acquire_many([target], "legacy", "direct-caller", 60) +                check("legacy API без epoch остаётся совместим", +                      legacy.ok and lease.release_session("legacy") == 1) +            else: +                check("старая DB мигрирует in-place и сохраняет lease", False, +                      "old module has no epoch parameter") +                check("3x24 конкурентных first-open безопасно мигрируют старую DB", False, +                      "old module has no epoch migration") +                check("mutation: migration без lock/recheck обязана стать красной", False, +                      "old module has no epoch migration") +                check("живая старая эпоха блокирует новую эпоху того же session", False, +                      "old module has no epoch parameter") +                check("новая эпоха проходит после точного release старой", False, +                      "old module has no epoch parameter") +                check("TTL/reaper даёт тому же session новую эпоху", False, +                      "old module has no epoch parameter") +                check("чужой release не снимает новую эпоху", False, +                      "old module has no epoch parameter") +                check("поздний release старой эпохи не снимает новую", False, +                      "old module has no epoch parameter") +                check("точный release новой эпохи снимает", False, +                      "old module has no epoch parameter") +                check("mutation: unfenced release обязан стать красным", False, +                      "detector requires epoch-aware API") +                check("fenced release проходит тот же detector", False, +                      "detector requires epoch-aware API") +                check("legacy release без token не снимает fenced lease", False, +                      "old module has no epoch parameter") +                check("legacy API без epoch остаётся совместим", True)
 
         help_run = subprocess.run([sys.executable, lease.__file__, "--help"],
                                   capture_output=True, text=True, timeout=30) @@ -147,9 +679,20 @@ def main():
               os.path.isfile(counter) and os.path.getsize(counter) > 0)
         if os.path.isfile(counter):
             rows = [json.loads(line) for line in open(counter, encoding="utf-8") if line.strip()] -                check("counter содержит allow и blocked", +                check("counter содержит acquired и blocked",
                   any(r.get("outcome") == "acquired" for r in rows) and
                   any(r.get("outcome") == "blocked" for r in rows)) +                check("counter различает released и epoch-mismatch", +                      any(r.get("event") == "release" and +                          r.get("outcome") == "released" for r in rows) and +                      any(r.get("event") == "release" and +                          r.get("outcome") == "epoch-mismatch" for r in rows)) +            leaks = _production_counter_leaks( +                production_counter, production_counter_before, root) +            check("тест не пишет боевой usage-counter", not leaks, +                  "test-attributed rows=%r; production bytes %d->%d" % +                  (leaks[:3], production_counter_before, +                   _file_size(production_counter)))
 finally:
     shutil.rmtree(root, ignore_errors=True)

@@ -165,5 +708,8 @@ if name == “main”: except Exception: pass if len(sys.argv) > 1 and sys.argv[1] == “–child”:

Вход/выход

Кто дёргает hooks/workspace_write_guard.py из общего PreToolUse; turnstate_hook.py @@ -21,18 +22,20 @@ не лежит в Syncthing: это mutex одного узла. Между машинами остаётся onair/шина.

Страховка

Счётчик %LOCALAPPDATA%/AntonAgents/workspace-write-leases/usage.jsonl; тесты обязаны перенаправлять его через WORKSPACE_WRITE_LEASE_COUNTER.

Тест

-updated: 2026-09-11 +updated: 2026-09-21 “”” from future import annotations

@@ -171,6 +174,28 @@ def is_wide_root(path): return value in wide_roots()

+def _migrate_epoch(con):

def _rows(con, now): return [dict(row) for row in con.execute(

@@ -205,12 +234,19 @@ class LeaseResult: conflicts: list[dict] = field(default_factory=list) reaped: int = 0 reason: str = “”

+def acquire_many(paths, session, agent, ttl_sec=DEFAULT_TTL_SEC, *, cwd=None, note=””,

-def acquire_many(paths, session, agent, ttl_sec=DEFAULT_TTL_SEC, *, cwd=None, note=””, now=None):

-def release_session(session): +def release_session(session, *, epoch=None):

@@ -343,8 +408,12 @@ def main(argv=None): acq.add_argument(“–cwd”, default=None) acq.add_argument(“–ttl-sec”, type=int, default=DEFAULT_TTL_SEC) acq.add_argument(“–note”, default=””)

-LOG = Path(os.path.expanduser(r”~.claude\hooks_constitution_guard.log”)) +LOG = Path(os.environ.get(“CONSTITUTION_GUARD_LOG”) or

# Protected = the constitution codex + the vault git internals. Substring match on the # resolved path, case-insensitive. EXTEND this list as the constitution grows. diff –git a/turnstate_hook.py b/turnstate_hook.py index 8b9852c..a68bcf0 100644 — a/turnstate_hook.py +++ b/turnstate_hook.py @@ -76,20 +76,23 @@ FILE_TOOLS = {“Write”, “Edit”, “MultiEdit”, “NotebookEdit”}

def release_workspace_write_lease(data):

diff –git a/workspace_write_guard.py b/workspace_write_guard.py index 1ac31b5..2b8482b 100644 — a/workspace_write_guard.py +++ b/workspace_write_guard.py @@ -15,15 +15,20 @@ reports canonical apply_patch; Claude reports Write/Edit/MultiEdit/NotebookEdi одну правку в получасовой замок на всю папку. Замер 14.09 после половинчатой утренней правки: 198 блокировок, 137 (69%) – запрос ПАПКИ, 86 из них ~/.claude/scripts, 36 пострадавших сессий, включая ЧТЕНИЕ файла и cd, который запирал сессию насмерть. -Честная дыра: запись по ОТНОСИТЕЛЬНОМУ пути из скрипта (python build.py) не ловится. -Её не ловили и раньше – при контейнерной cwd, то есть в обычном случае. - -Input: hook JSON on stdin. Output: silence on allow; stderr + rc=2 on deny. +Честные дыры: shell-команда с относительной целью (Set-Content ./x) не ловится, +потому что guard извлекает только названные абсолютные пути; файл, который скрыто +пишет дочерний скрипт (python build.py), тем более не виден. Межмашинной защиты +здесь нет: SQLite – mutex только этого узла; между машинами остаются +On Air/шина/worktree ownership. Извлечение абсолютного пути здесь Windows-only +(C:\\...); перенос этого hook на Unix без отдельного path-parser не защищает /tmp/.... + +Input: hook JSON on stdin. Output: silence on allow; Claude deny is stderr + rc=2, +while Codex payloads with turn/tool ids receive structured deny on stdout + rc=0. Caller: trusted constitution_guard.cmd (PreToolUse) and turnstate_hook.py (Stop). Rail: local Python + scripts/workspace_write_lease.py, 0 LLM, 0 network. -Test: _test_lease_storm.py (класс), _test_codex_shared_workspace_safety.py, +Test: _test_lease_storm.py (класс), _test_workspace_write_guard.py, _test_workspace_write_lease.py. -updated: 2026-09-14 +updated: 2026-09-21 “”” from future import annotations

@@ -35,7 +40,8 @@ import sys from dataclasses import dataclass, field from pathlib import Path

-SCRIPTS = os.path.join(os.path.expanduser(“~”), “.claude”, “scripts”) +SCRIPTS = os.environ.get(“WORKSPACE_WRITE_LEASE_MODULE_DIR”) or os.path.join(

+def canonical_session_id(value):

+def lease_epoch(payload):

+def _top_level_segments(command, dialect=”powershell”):

@@ -141,10 +359,11 @@ def shell_is_read_only(command): command = str(command or “”).strip() if not command: return True

@@ -163,9 +382,10 @@ def executable_tokens(command): интерпретатора снимаем ещё и первый нефлаговый аргумент. Цель записи (log) остаётся. “”” out = set()

def release_payload(payload): sid = session_id(payload)

def emit_codex_deny(payload, reason): — HOOKS DIFF END —


Claude:

SUMMARY: A large, well-engineered diff adding fencing-epoch semantics to the workspace write lease plus a much stronger integration test suite (real CLI contention, live hooks.json execution, concurrent schema migration, and multiple production-source mutants with self-verifying string matches). The core atomic-acquire/BUSY/fencing logic looks correct and is genuinely exercised, but the “legacy migration handoff” fallback in release_session is a standing (not migration-scoped) broad-delete path that can consume the wrong lease.

FINDINGS:

===VERDICT=== REQUEST_CHANGES