Skip to the content.

Антон:

You are an independent adversarial reviewer. Review ONLY these immutable commits and do not edit files:

Acceptance: local Windows mutex for Claude/Codex physical absolute file targets; exactly one winner under concurrency; loser BUSY rc=7 with holder; foreign release cannot remove; delayed old turn release cannot remove new epoch; legacy-schema migration is concurrent-safe and distinguishes migrated rows from fresh empty-epoch leases; Codex aliases normalize; hooks cover apply_patch plus Bash/PowerShell/exec_command/shell; quoted child shells, env prefixes, PowerShell accepted Command prefixes/slash form, and outer-shell dialects do not bypass or false-block; cwd/executable lease-storm stays fixed. Tests must be isolated, mutate production-source/config semantics, and keep counters/audit isolated. Honest non-goals: local-only; physical paths only; relative targets and hidden child-script writes remain gaps; no role/question authorization.

Try to disprove the claims from code, especially transaction semantics, migration handoff, release fencing, parser escaping/wrappers, hook structured-deny contract, and whether tests would fail on broken variants. Distinguish actionable defects from out-of-scope limits. Respond exactly: SUMMARY: … FINDINGS:

=== SCRIPTS COMMIT === commit e98a662cbb0d92e95dd528a19617e89c1d437db1 Author: Claude Config Snapshot (HUB-01) claude-config@HUB-01.local AuthorDate: Mon Sep 21 07:16:30 2026 +0100 Commit: claude-? <claude+?@local> CommitDate: Mon Sep 21 08:27:16 2026 +0100

fix: fence workspace write leases by turn

Assisted-by: Codex CLI / GPT-5.6

Machine: HUB-01

Account: bb

Operator: Anton ---  _test_codex_shared_workspace_safety.py | 273 ----------  _test_lease_storm.py                   |  12 +-  _test_workspace_write_guard.py         | 894 +++++++++++++++++++++++++++++++++  _test_workspace_write_lease.py         | 677 +++++++++++++++++++++++--  workspace_write_lease.py               | 139 ++++-  5 files changed, 1661 insertions(+), 334 deletions(-)

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=””):

РЕЛЬСА: 0 LLM, 0 сети, stdlib. Гоняет реальный hooks/workspace_write_guard.py через stdin-контракт. -updated: 2026-09-14 +updated: 2026-09-21 “”” import json import os @@ -41,7 +41,9 @@ import subprocess import sys import tempfile

-HOOK = os.path.join(os.path.expanduser(“~”), “.claude”, “hooks”, “workspace_write_guard.py”) +HOOKS = os.environ.get(“WORKSPACE_LEASE_TEST_HOOKS”) or os.path.join(

if name == “main”:

-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"))) +                check("поздний token Stop не снимает свежую legacy epoch", +                      _delayed_token_preserves_fresh_legacy( +                          lease, os.path.join(root, "fresh-legacy-target.txt"))) +                if hasattr(lease, "LEGACY_MIGRATED_EPOCH"): +                    broad_mutant_file = _broad_legacy_delete_mutant( +                        lease.__file__, os.path.join( +                            root, "workspace_write_lease_broad_legacy_mutant.py")) +                    original_db = os.environ["WORKSPACE_WRITE_LEASE_DB"] +                    os.environ["WORKSPACE_WRITE_LEASE_DB"] = os.path.join( +                        root, "broad-legacy-mutant.sqlite3") +                    try: +                        broad_mutant = _load_candidate(broad_mutant_file, "broadlegacy") +                        marker_delete_killed = not _delayed_token_preserves_fresh_legacy( +                            broad_mutant, os.path.join(root, "broad-legacy-target.txt")) +                    finally: +                        os.environ["WORKSPACE_WRITE_LEASE_DB"] = original_db +                else: +                    marker_delete_killed = False +                check("mutation: marker-delete нельзя расширить до fresh empty epoch", +                      marker_delete_killed) +                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 +744,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 +773,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

@@ -49,6 +52,10 @@ from datetime import datetime, timezone

DEFAULT_TTL_SEC = int(os.environ.get(“WORKSPACE_WRITE_LEASE_TTL_SEC”) or 1800) BUSY_TIMEOUT_MS = 10000 +# Rows that existed before the epoch column was added must be distinguishable from +# new legacy callers that intentionally acquire with epoch=””. A token-aware +# Stop may consume this one-time handoff marker, but must never wildcard fresh rows. +LEGACY_MIGRATED_EPOCH = “workspace_lease_legacy_migrated_v1”

def _state_dir(): @@ -171,6 +178,30 @@ 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,14 +240,31 @@ 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 +426,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=””)

=== HOOKS COMMIT === commit d23e6c57d67ab6133bc2fa305306341affc93779 Author: Claude Config Snapshot (HUB-01) claude-config@HUB-01.local AuthorDate: Mon Sep 21 07:16:30 2026 +0100 Commit: claude-? <claude+?@local> CommitDate: Mon Sep 21 08:27:22 2026 +0100

fix: preserve fenced workspace leases across turns

Assisted-by: Codex CLI / GPT-5.6

Machine: HUB-01

Account: bb

Operator: Anton ---  constitution_guard.py    |   5 +-  turnstate_hook.py        |  13 +-  workspace_write_guard.py | 318 +++++++++++++++++++++++++++++++++++++++++++----  3 files changed, 307 insertions(+), 29 deletions(-)

diff –git a/constitution_guard.py b/constitution_guard.py index 0b454c3..ce1b395 100644 — a/constitution_guard.py +++ b/constitution_guard.py @@ -7,7 +7,7 @@ in bypass mode and only loads at session start). Anton 2026-06-21 (“замок

Mechanism: block Edit/Write/MultiEdit/apply_patch on a protected path = exit code 2 + reason on stderr. Then acquire the same atomic per-path lease for Claude and Codex; -explicitly mutating shell commands conservatively lease their cwd. +explicitly mutating shell commands lease only named absolute targets (never cwd). Escape hatch for a REAL, Anton-authorized constitution change (two ways): (a) set env CONSTITUTION_UNLOCK=1 in the shell that launched Claude, or (b) change the file via a deliberate Bash/python direct write (this guards the Edit/ @@ -19,7 +19,8 @@ from future import annotations import json, os, sys, time from pathlib import Path

-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..13c6a35 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 _shell_dialect(tool):

+def lease_epoch(payload):

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

@@ -153,7 +416,7 @@ INTERPRETERS = re.compile( r”zsh|ruby|perl|php|dotnet|java|uv|uvx|npx)(?:.exe)?$”)

-def executable_tokens(command): +def executable_tokens(command, dialect=”powershell”): “"”ЗАПУСКАЕМОЕ – не цель записи: первый токен сегмента и скрипт интерпретатора.

 Замер 14.09: интерпретатор `python.exe` лизовался 5 раз, `cron_heartbeat.py` -- 5, @@ -163,9 +426,10 @@ def executable_tokens(command):
 интерпретатора снимаем ещё и первый нефлаговый аргумент. Цель записи (`log`) остаётся.
 """
 out = set() -    for segment in SEGMENT_SPLIT.split(str(command or "")): -        tokens = re.findall(r"\"([^\"]+)\"|'([^']+)'|(\S+)", segment.strip()) -        tokens = [next(g for g in t if g) for t in tokens] +    for segment in _top_level_segments(command, dialect): +        tokens = [value for value, _quoted in _shell_args(segment, dialect)] +        if tokens and tokens[0] == "&": +            tokens = tokens[1:]
     if not tokens:
         continue
     out.add(tokens[0]) @@ -201,7 +465,8 @@ def extract_intent(payload):
     return Intent(True, sorted(set(paths)), "file-tool")
 if tool in SHELL_TOOLS:
     command = str(inp.get("command") or inp.get("cmd") or "") -        if shell_is_read_only(command): +        dialect = _shell_dialect(tool) +        if shell_is_read_only(command, dialect=dialect):
         return Intent(False, [], "read-only-shell")
     # 14.09.2026, класс lease-storm, второй заход. Утреннее лечение вывело из лизы только
     # КОНТЕЙНЕРЫ (глубина<=1 и реестр home_dirs.json), но cwd продолжала лизоваться, если @@ -211,14 +476,15 @@ def extract_intent(payload):
     # флота: сессия, правящая ОДИН скрипт, запирала все остальные на 30 минут.
     # Новый контракт в одну строку: shell лизует ТОЛЬКО файлы, которые НАЗЫВАЕТ, и только
     # когда ВИДИМО пишет. cwd не лизуется никогда -- ни широкая, ни узкая. -        if not SHELL_MUTATION.search(command): +        if not SHELL_MUTATION.search(shell_scan_text(command, dialect=dialect)):
         # Не читающая (словарь не признал), но и признака записи нет: `python build.py`,
         # `node x.js`, heredoc. Лизовать нечего -- цели записи в тексте команды нет.
         # Честная дыра: такая команда может писать по относительному пути, и мы её не
         # поймаем. Она и раньше не ловилась, когда cwd была контейнером (обычный случай).
         lease.count_event("hook", "no-mutation-marker", {"tool": tool_name(payload)})
         return Intent(False, [], "no-mutation-marker") -        skip = {_safe_resolve(token, cwd) for token in executable_tokens(command)} +        skip = {_safe_resolve(token, cwd) +                for token in executable_tokens(command, dialect=dialect)}
     paths = []
     for rx in (ABS_QUOTED, ABS_BARE):
         for match in rx.finditer(command): @@ -255,24 +521,32 @@ def guard_pre(payload):
     lease.count_event("hook", "blocked-no-session", {"tool": tool_name(payload)})
     return Decision(False, "write hook has no session_id; cannot own a lease", intent.paths)
 try: +        epoch = lease_epoch(payload)
     result = lease.acquire_many(intent.paths, sid, agent_name(payload),
                                 cwd=str(payload.get("cwd") or ""), -                                    note="PreToolUse %s" % tool_name(payload)) +                                    note="PreToolUse %s" % tool_name(payload), +                                    epoch=epoch)
 except Exception as exc:
     lease.count_event("hook", "blocked-error", {"tool": tool_name(payload),
                       "session": sid, "error": type(exc).__name__})
     return Decision(False, "lease engine failed: %s" % type(exc).__name__, intent.paths)
 if result.ok: +        lease.count_event("hook", "acquired", {"tool": tool_name(payload), +                          "session": sid, "epoch": epoch, +                          "resources": intent.paths})
     return Decision(True, "lease acquired", intent.paths)
 owners = sorted({str(row.get("session") or "?") for row in result.conflicts})
 held = sorted({str(row.get("resource") or "?") for row in result.conflicts}) +    lease.count_event("hook", "blocked", {"tool": tool_name(payload), +                      "session": sid, "epoch": epoch, "resources": intent.paths, +                      "held_by": owners})
 return Decision(False, "BUSY: %s held by %s" % (", ".join(held), ", ".join(owners)),
                 intent.paths)

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

def emit_codex_deny(payload, reason):


Bash – {“command”: “cd ~/.claude/scripts 2>/dev/null && git log –oneline -3 – workspace_write_lease.py 2>&1; echo "—"; cd ~/.claude/hooks 2>/dev/null && git log –oneline -3 – workspace_write_guard.py 2>&1”, “description”: “Check git history for the two changed files”}


Bash – {“command”: “cd ~/.claude && git cat-file -t e98a662cbb0d92e95dd528a19617e89c1d437db1 2>&1; git cat-file -t d23e6c57d67ab6133bc2fa305306341affc93779 2>&1; git rev-parse HEAD 2>&1; git log –oneline -1 2>&1”, “description”: “Check if the commit hashes exist in the local repo”}


Bash – {“command”: “cd ~/.claude/scripts && grep -n "updated:" workspace_write_lease.py head -3\necho "—guard—"\ncd ~/.claude/hooks && grep -n "updated:" workspace_write_guard.py head -3\necho "—lease-storm test—"\ncd ~/.claude/scripts && grep -n "updated:" _test_lease_storm.py head

Bash – {“command”: “set -e\nTMP=$(mktemp -d /tmp/wwl-review.XXXXXX)\necho "TMP=$TMP"\nmkdir -p "$TMP/scripts" "$TMP/hooks"\ncp ~/.claude/scripts/workspace_write_lease.py "$TMP/scripts/"\ncp ~/.claude/scripts/_test_lease_storm.py "$TMP/scripts/"\ncp ~/.claude/scripts/_test_workspace_write_lease.py


Write – {“file_path”: “/tmp/wwl-review.VgSFOY/scripts.patch”, “content”: “diff –git a/_test_lease_storm.py b/_test_lease_storm.py\nindex b2ff6e02b..1c75bda20 100644\n— a/_test_lease_storm.py\n+++ b/_test_lease_storm.py\n@@ -33,7 +33,7 @@\n проверка 1 обязана ОТБИТЬСЯ. Тест, который не краснеет на сл


Bash – {“command”: “powershell -c "Write-Output HELLO_FROM_DASH_C" 2>&1\necho "—"\npwsh -c "Write-Output HELLO_FROM_PWSH_DASH_C" 2>&1\necho "—exit codes above—"”, “description”: “Empirically test whether -c alone works as abbreviation for -Command in powershell/pwsh”}


Bash – {“command”: “powershell -Command:"Write-Output COLON_FORM_TEST" 2>&1\necho "rc=$?"\necho "—testing pwsh—"\npwsh -Command:"Write-Output COLON_FORM_TEST_PWSH" 2>&1\necho "rc=$?"”, “description”: “Test whether colon-attached -Command: syntax is accepted by powershell/pwsh native host pars


Claude:

SUMMARY: Deep static/empirical review of both commits (transaction semantics, migration concurrency, release fencing, PowerShell/Bash parsing, structured-deny contract). Reconstructed the pre-commit base and diffed logically since these commit hashes aren’t present in the local ~/.claude repo (local HEAD is still 2026-09-11/14-dated, this commit hasn’t landed yet). Verified empirically on this machine that powershell -c "..." and pwsh -c "..." are accepted (confirms _powershell_command_flag’s bare -c acceptance is correct, not over-broad) and that -Command:"..." colon-attached form is rejected by both native hosts (confirms the guard correctly does not need to special-case it — no bypass there). Traced acquire_many’s BEGIN IMMEDIATE + recheck-after-lock pattern, _migrate_epoch’s lock-then-recheck schema upgrade, _same_owner’s exact-epoch-or-migrated-marker-adoption rule, and release_session’s exact-epoch delete plus scoped legacy-marker handoff — all algebraically correct against the traced test scenarios (delayed-old-epoch-release, foreign-session release, CLI epoch plumbing, broad-legacy-delete mutant). The one substantive gap found: the turn-fencing protection this commit is titled for (“fence workspace write leases by turn”) is proven only for Codex. Claude sessions never populate turn_id, so lease_epoch() is always "" for every Claude turn of a session — meaning _same_owner trivially matches across turns and a delayed/late Stop from a stale Claude turn can still release a lease a newer Claude turn just acquired, identical to pre-patch behavior. This is disclosed in an inline code comment but is not listed among the task’s stated “Honest non-goals.”

FINDINGS:

VERDICT: REQUEST_CHANGES