# Bug Report: add-state-loop-lock Bug_find phase observations. Each observation is non-blocking unless marked BLOCKER. ## O1 — `print(... state['resumed_count'] ...)` after `with _loop_lock` exits, status.py:cmd_approve_loop `cmd_approve_loop` references `state['resumed_count']` AFTER the `with` block exits. `state` is in function scope and was assigned inside the with block; the value is the post-mutation dict. **Not a bug** — confirmed by tracing the variable lifecycle. Safe. ## O2 — `_disable_schedule` / `_enable_schedule` left OUTSIDE the lock for pause/resume/approve; INSIDE for halt For `cmd_pause_loop` / `cmd_resume_loop` / `cmd_approve_loop`, `_disable_schedule` / `_enable_schedule` is called AFTER the `with _loop_lock` block exits (line ~1950 area, after the lock releases). For `cmd_check_gate`'s `_halt_loop` call, `_disable_schedule` is called INSIDE the lock (since `_halt_loop` couples the halt-write with the schedule disable). **Transient**: between the loop's `.state.loop` write (inside the lock) and the subsequent OS schedule unit disable (outside the lock), the OS scheduler could fire another tick. That tick's `_gate` subprocess reads `status=paused` and exits 0 (clean scheduler self-skip). So no real over-tick — just a no-op tick for ~100ms. Same for resume/approve. **Not a bug** — documented behavior; matches SPEC R4 (idempotence inside the lock scope; OS-level schedule toggles are out-of-band best-effort). The transient inconsistency is harmless because `--check-gate` already self-skips on non-running. ## O3 — `_gate` subprocess acquires status.py's `_loop_lock`, honors env-var bypass If a future caller of `status.py --check-gate` manually sets `$AUTOMATON_NO_LOOP_LOCK=1` in their shell, `_loop_lock` becomes a no-op even when invoked standalone. **Not a bug**: the env var is a documented escape hatch; a manual user who sets it accepts that the lock is bypassed. The runner's own subprocess env is private to the subprocess (passed via the `env` kwarg to `subprocess.run` in `_run_json` invoked from `_gate`). The harness subprocesses do NOT inherit the var (verified: `subprocess.run` without `env` inherits `os.environ`, which is unmodified at runner top level). Risk assessment: HIGH only if a user wraps `status.py` invocations with `AUTOMATON_NO_LOOP_LOCK=1` AND expects pause-loop / approve-loop / check-gate invocations to serialize. Documented in `_loop_lock`'s docstring. **Not a bug** — escape hatch has explicit semver-stable contract. ## O4 — `_loop_lock` is non-re-entrant across processes POSIX `flock` is per-fd-per-process: a second process blocks cleanly waiting for the first to release. POSIX `flock` IS re-entrant within a single process on a single fd. Windows `msvcrt.locking` is NOT re-entrant within a single process (would deadlock on re-acquire). Documented in `_loop_lock`'s docstring. Audit shows no nested `_loop_lock` callsites. **Not a bug** — explicitly forbidden by the SPEC ("Audit every callsite to ensure no nested `_loop_lock` within the same `with` block"). Audited in IMPLEMENTATION.md's "NESTED-LOCK AUDIT" section. ## O5 — `cmd_tick`'s lock scope includes the entire harness subprocess run The runner holds `_loop_lock` across the long-running Implement/Verify/Orchestrate harness subprocesses. A concurrent `--pause-loop` invoked by an operator will block for the WHOLE tick duration (potentially minutes). The harness is unaware of `_loop_lock` and cannot signal the operator to wait gracefully. **Documented behavior** per SPEC D-L1: "ticks short; operator notices via `--loop-list` stale `last_tick_at`". If ticks grow long, future work could split the lock into a short gate-decision lock and a longer state-mutation lock. **Not a bug** — explicit v1.1 scope per `design/loops/BACKLOG.md` (out of scope for this task). ## Verdict No BLOCKERS. All observations are documented behaviors per SPEC + D-L6. Recommend proceeding to adversarial_bug_find.