diff --git a/plugins/disk-hygiene/.claude-plugin/plugin.json b/plugins/disk-hygiene/.claude-plugin/plugin.json
index 79241c891..c9a54dcdf 100644
--- a/plugins/disk-hygiene/.claude-plugin/plugin.json
+++ b/plugins/disk-hygiene/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "disk-hygiene",
- "version": "0.23.11",
+ "version": "0.23.12",
"description": "Context-aware disk hygiene for arbitrary directory trees: inventories orphaned and temporary artifacts, classifies evidence into review tiers, and offers exact-path cleanup only after a fresh safety preview and explicit per-tier approval. The target is read-only by default; OS-managed paths, links and mount points, VCS-tracked content without the complete checkout evidence bundle, changed entries, and live-handle uncertainty fail closed.",
"author": {
"name": "Melodic Software",
diff --git a/plugins/disk-hygiene/CHANGELOG.md b/plugins/disk-hygiene/CHANGELOG.md
index f3f1f6047..b96edd1ab 100644
--- a/plugins/disk-hygiene/CHANGELOG.md
+++ b/plugins/disk-hygiene/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to the `disk-hygiene` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.
+## [0.23.12]
+
+### Changed
+
+- **The `Stop` guard-launch monitor no longer starts Python in a session that launched no guard.** The engine-gate rows are `if`-gated on the engine's file name, so most sessions never run `destructive_guard.py` at all, yet the monitor started a whole interpreter every turn to discover that from the transcript. `Stop` rows accept neither `matcher` nor `if`, so the gate lives in `hooks/run-python-hook.sh`, which grows three optional leading flags: `--marker-root
`, `--launch-marker ` (write `//.launched` before exec'ing Python) and `--skip-unless-marker ` (exit 0 without exec'ing when a candidate marker directory exists and none of them holds that file). Process creations per `Stop` on Windows, measured with a job-object census at n=5: 5 before, 3 with no marker, and still 5 with one present, against a 1-creation harness floor; wall clock 271 ms to 120 ms on the skipped path. The engine-gate row pays nothing for the marker after the first launch in a plugin data root, which spends one `mkdir`.
+- Marker semantics: the marker is per session and is never removed, so a data root accumulates one empty file per session that launched a guard, and no retention sweep collects them; a session whose guard rows never fired is skipped by design; the marker is written before the interpreter is resolved, so a guard that launches and dies, the failure the monitor exists to report, still leaves it. With a marker present the monitor's `systemMessage` and its `guard-decisions` record are byte-identical to before. A skipped turn emits no telemetry envelope at all, where it previously emitted an `ok` one.
+- The `Stop` gate fails open on the half it can detect: it skips only when at least one candidate marker directory exists and holds no marker for the session, so a launch whose `mkdir` failed for both roots leaves nothing and every later `Stop` runs the monitor as it did before the flags. The cost is that the skip is inert in a plugin data root where no guard has ever launched, until the first launch spends its `mkdir`: 5 creations with no candidate directory, 3 with an empty one, 5 with a marker present. The residual it cannot detect: a candidate directory that exists while the marker file itself could not be written (a full disk, a permission denial on the file alone) still silences the monitor for that session, because that file is the only channel between the launch row and the `Stop` row.
+
## [0.23.11]
### Changed
diff --git a/plugins/disk-hygiene/hooks/hooks.json b/plugins/disk-hygiene/hooks/hooks.json
index 2c606707f..a51996d63 100644
--- a/plugins/disk-hygiene/hooks/hooks.json
+++ b/plugins/disk-hygiene/hooks/hooks.json
@@ -7,7 +7,7 @@
"hooks": [
{
"type": "command",
- "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh --marker-root \"${CLAUDE_PLUGIN_DATA}\" --launch-marker guard-launch-monitor \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
"if": "Bash(*hygiene.py*)",
"shell": "bash",
"timeout": 60,
@@ -20,7 +20,7 @@
"hooks": [
{
"type": "command",
- "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh --marker-root \"${CLAUDE_PLUGIN_DATA}\" --launch-marker guard-launch-monitor \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
"if": "PowerShell(*hygiene.py*)",
"shell": "bash",
"timeout": 60,
@@ -28,7 +28,7 @@
},
{
"type": "command",
- "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh --marker-root \"${CLAUDE_PLUGIN_DATA}\" --launch-marker guard-launch-monitor \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
"if": "PowerShell(*python*$*)",
"shell": "bash",
"timeout": 60,
@@ -36,7 +36,7 @@
},
{
"type": "command",
- "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh --marker-root \"${CLAUDE_PLUGIN_DATA}\" --launch-marker guard-launch-monitor \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/destructive_guard.py --mode engine-gate --plugin-root \"${CLAUDE_PLUGIN_ROOT}\" --authorized-data-root \"${CLAUDE_PLUGIN_DATA}\"",
"if": "PowerShell(*& $*)",
"shell": "bash",
"timeout": 60,
@@ -50,7 +50,7 @@
"hooks": [
{
"type": "command",
- "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/guard_launch_monitor.py --data-root \"${CLAUDE_PLUGIN_DATA}\"",
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/run-python-hook.sh --marker-root \"${CLAUDE_PLUGIN_DATA}\" --skip-unless-marker guard-launch-monitor \"${CLAUDE_PLUGIN_ROOT}\"/skills/clean/scripts/guard_launch_monitor.py --data-root \"${CLAUDE_PLUGIN_DATA}\"",
"shell": "bash",
"timeout": 20,
"statusMessage": "Checking for silent destructive-guard failures..."
diff --git a/plugins/disk-hygiene/hooks/run-python-hook.sh b/plugins/disk-hygiene/hooks/run-python-hook.sh
index ceea8e718..6b6ae6743 100755
--- a/plugins/disk-hygiene/hooks/run-python-hook.sh
+++ b/plugins/disk-hygiene/hooks/run-python-hook.sh
@@ -81,6 +81,93 @@ except OSError:
raise SystemExit(0 if sys.version_info >= floor else 1)
'
+# --- optional per-session launch marker ------------------------------------
+#
+# Three OPTIONAL leading flags, consumed here and never forwarded to Python:
+#
+# --marker-root root the marker tree lives under, spelled
+# `"${CLAUDE_PLUGIN_DATA}"` by every caller;
+# --launch-marker write `//.launched`
+# BEFORE exec'ing the target;
+# --skip-unless-marker exit 0 without exec'ing anything when at
+# least one candidate directory exists and
+# none of them holds that file.
+#
+# Together they let a `Stop` hook cost nothing in a session where the hook it
+# watches never ran. `guard_launch_monitor.py` reports failures of the
+# `PreToolUse` guard, and the guard rows are `if`-gated on the engine's file
+# name, so most sessions never launch the guard at all — yet the monitor still
+# started a whole Python on every turn to discover that. `Stop` rows accept
+# neither `matcher` nor `if`, so the only place that gate can live is here.
+#
+# The root is passed EXPLICITLY rather than read from `CLAUDE_PLUGIN_DATA` in
+# the environment: a hook subprocess can inherit that variable naming a
+# DIFFERENT plugin's data directory, and a writer and a reader that disagree
+# about the root skip silently, which is the missed-detection failure this
+# monitor exists to prevent. An empty value, or an unsubstituted literal
+# `${CLAUDE_PLUGIN_DATA}`, is treated as absent (the placeholder idiom
+# `guard_launch_monitor.py` already uses) and the tmp fallback carries the
+# marker alone.
+#
+# Failure modes, stated rather than implied:
+# * a marker is per session and is never removed. Under the tmp fallback the
+# OS clears it; under `--marker-root` it is one empty file per session that
+# launched a guard, and neither the clean engine nor `lib/guard_decision_log.py`
+# runs a retention sweep over the plugin data root that could collect them.
+# * a session whose guard rows never fired is skipped BY DESIGN: there is no
+# guard invocation for the monitor to have found a failure of.
+# * a hook that failed before this script ran leaves no marker and is not this
+# monitor's to detect — the detector reads the transcript for the guard's
+# own command string, which such a failure still records.
+# * the marker is written before the interpreter is even resolved, so the
+# failure the monitor DOES exist to catch — the guard launching and dying —
+# still leaves the marker that keeps the monitor running.
+# * the session id is recovered with a regex over the raw payload rather than
+# a JSON parse, because a parse is the Python this gate exists to avoid.
+# The match is anchored to the payload's opening key, which nothing further
+# in the payload can reach; only a payload that does not open with
+# `session_id` falls back to an unanchored match, where a NESTED
+# `session_id` key could win instead and key the marker wrongly. Both rows
+# parse identically, so that corner is a missed detection, never a false
+# alarm.
+# * a misparse fails SAFE: no session id means write nothing and skip
+# nothing, which is this launcher's behavior before these flags existed.
+# * a launch that could create NEITHER candidate directory leaves no marker
+# at all, and a Stop that read only the marker file would then silence the
+# monitor for the whole session: a silent failure in the one detector that
+# exists to catch silent failures. So the skip is gated on a candidate
+# DIRECTORY existing. With none, the Stop row falls through and runs the
+# monitor, which is the behavior before these flags existed. The cost of
+# that fail-open is that the skip is inert in a plugin data root where no
+# guard has ever launched: every Stop pays the old price until the first
+# guard launch spends its one `mkdir`.
+# * RESIDUAL, and undetectable across processes: a candidate directory that
+# exists while the marker FILE could not be written (a full disk, a
+# permission denial on the file alone) still degrades to silence for that
+# session. The file that failed is the only channel between the launch row
+# and the Stop row, so the Stop row cannot tell that case apart from a
+# session whose guard rows never fired.
+MARKER_ROOT=""
+LAUNCH_MARKER_SUBDIR=""
+SKIP_MARKER_SUBDIR=""
+while (($#)); do
+ case "$1" in
+ --marker-root)
+ MARKER_ROOT="${2:-}"
+ shift 2 || break
+ ;;
+ --launch-marker)
+ LAUNCH_MARKER_SUBDIR="${2:-}"
+ shift 2 || break
+ ;;
+ --skip-unless-marker)
+ SKIP_MARKER_SUBDIR="${2:-}"
+ shift 2 || break
+ ;;
+ *) break ;;
+ esac
+done
+
SCRIPT="${1:-}"
shift || true
@@ -90,6 +177,86 @@ case "$SCRIPT" in
*) ;;
esac
+# shellcheck disable=SC2016 # the literal placeholder, deliberately unexpanded
+_DATA_ROOT_PLACEHOLDER='${CLAUDE_PLUGIN_DATA}'
+_MARKER_PATHS=()
+_PAYLOAD=""
+_SESSION_ID=""
+_BUFFERED_STDIN=0
+
+# Buffer the whole hook payload into a variable and recover its session id.
+# `read` is a builtin, so buffering costs no process; the payload is handed to
+# Python on a here-string at `exec` time, leaving its stdin unchanged.
+_read_payload() {
+ _BUFFERED_STDIN=1
+ IFS= read -r -d '' _PAYLOAD || true
+ local field='"session_id"[[:space:]]*:[[:space:]]*"([^"\\]*)"'
+ # Anchored first. The hooks reference shows `session_id` as the payload's
+ # opening key for every event but documents no ordering guarantee, and the
+ # payload nests objects of its own (`tool_input`) that could carry the same
+ # key name. An anchored match cannot be reached by either; the unanchored
+ # fallback runs only for a payload that did NOT open with the field, and keeps
+ # a reordered payload from turning this gate into a silent no-op.
+ local anchored="^[[:space:]]*[{][[:space:]]*$field"
+ if [[ "$_PAYLOAD" =~ $anchored ]] || [[ "$_PAYLOAD" =~ $field ]]; then
+ _SESSION_ID="${BASH_REMATCH[1]}"
+ fi
+}
+
+# Fill `_MARKER_PATHS` with the candidates for one subdir, most-preferred
+# first, mirroring `guard_launch_monitor.py`'s own `_marker_path_candidates`:
+# the data root when one resolved, then a tmp fallback whose directory name is
+# built from the subdir, so a `.launched` marker sits beside the `.warned` one
+# the monitor writes. Only bash reads and writes `.launched`, so the tmp
+# directory need not be the one Python's `tempfile.gettempdir()` picks; what
+# matters is that both flags resolve it identically, which they do.
+_marker_candidates() {
+ local subdir="$1" session="$2"
+ local safe="${session//[^a-zA-Z0-9_-]/_}"
+ _MARKER_PATHS=()
+ [[ -n "$safe" ]] || return 1
+ if [[ -n "$MARKER_ROOT" && "$MARKER_ROOT" != "$_DATA_ROOT_PLACEHOLDER" ]]; then
+ _MARKER_PATHS+=("$MARKER_ROOT/$subdir/$safe.launched")
+ fi
+ _MARKER_PATHS+=("${TMPDIR:-/tmp}/disk-hygiene-$subdir/$safe.launched")
+}
+
+if [[ -n "$SKIP_MARKER_SUBDIR" ]]; then
+ _read_payload
+ if [[ -n "$_SESSION_ID" ]] && _marker_candidates "$SKIP_MARKER_SUBDIR" "$_SESSION_ID"; then
+ _marker_found=0
+ # Skipping requires POSITIVE evidence that the launch side got as far as a
+ # marker tree. No candidate directory at all is the shape a launch whose
+ # `mkdir` failed for every root leaves behind, and silencing the monitor on
+ # it would be a silent failure in the detector that exists to catch silent
+ # failures. Both tests are builtins, so the skip path still spawns nothing.
+ _marker_dir_seen=0
+ for _marker_path in "${_MARKER_PATHS[@]}"; do
+ [[ -d "${_marker_path%/*}" ]] && _marker_dir_seen=1
+ if [[ -f "$_marker_path" ]]; then
+ _marker_found=1
+ break
+ fi
+ done
+ if ((_marker_dir_seen && !_marker_found)); then
+ exit 0
+ fi
+ fi
+fi
+
+if [[ -n "$LAUNCH_MARKER_SUBDIR" ]]; then
+ ((_BUFFERED_STDIN)) || _read_payload
+ if [[ -n "$_SESSION_ID" ]] && _marker_candidates "$LAUNCH_MARKER_SUBDIR" "$_SESSION_ID"; then
+ for _marker_path in "${_MARKER_PATHS[@]}"; do
+ _marker_dir="${_marker_path%/*}"
+ # `mkdir` is the one spawn on this path and `[[ -d ]]` is a builtin, so it
+ # is paid once per root rather than once per launch.
+ [[ -d "$_marker_dir" ]] || mkdir -p "$_marker_dir" 2>/dev/null || continue
+ : >"$_marker_path" 2>/dev/null && break
+ done
+ fi
+fi
+
# Portable WindowsApps path-component check (case-insensitive).
_under_windowsapps() {
[[ "${1,,}" == *windowsapps* ]]
@@ -315,4 +482,13 @@ if [[ -z "$PYTHON" ]]; then
exit 0
fi
+# A buffered payload is replayed on a here-string, which bash serves from a pipe
+# or a temp file without creating a process. Python reads the same bytes it
+# would have read from the inherited stdin, plus the newline `<<<` appends —
+# both consumers here (`json.load(sys.stdin)` in the guard, `sys.stdin.read()`
+# then `json.loads` in the monitor) ignore trailing whitespace.
+if ((_BUFFERED_STDIN)); then
+ exec "$PYTHON" "$SCRIPT" "$@" <<<"$_PAYLOAD"
+fi
+
exec "$PYTHON" "$SCRIPT" "$@"
diff --git a/plugins/disk-hygiene/hooks/run-python-hook.test.sh b/plugins/disk-hygiene/hooks/run-python-hook.test.sh
index b9d0c7715..3add1588d 100755
--- a/plugins/disk-hygiene/hooks/run-python-hook.test.sh
+++ b/plugins/disk-hygiene/hooks/run-python-hook.test.sh
@@ -381,7 +381,10 @@ done
FAKE_BIN="$(mktemp -d)"
# Replaces (does not chain onto) the earlier EXIT trap, so it cleans up both.
trap 'rm -rf "$FAKE_BIN" "$PROBE_DIR"' EXIT
-for stub in python3 python; do
+# `py` belongs in the stub set with the other two: the launcher's third branch
+# resolves through the Windows `py` launcher, and a host that has one resolves a
+# real interpreter here, runs the monitor, and sees no systemMessage at all.
+for stub in python3 python py; do
printf '#!/usr/bin/env bash\nexit 127\n' >"$FAKE_BIN/$stub"
chmod +x "$FAKE_BIN/$stub"
done
@@ -458,4 +461,181 @@ else
"2" "$census_execs"
fi
+# --- the per-session launch marker gates the Stop monitor before python ---
+#
+# `Stop` rows accept neither `matcher` nor `if`, so the only place a "did the
+# guard run at all this session" gate can live is the launcher. The contract has
+# five halves: a launch records the session (even when the launched python then
+# dies, which is the very failure the monitor exists to report), a session
+# unrecorded in a marker tree that EXISTS reaches no python at all, a recorded
+# one runs exactly as before, a payload the launcher cannot key on falls back to
+# running python, and a marker tree that does not exist at all falls back the
+# same way rather than silencing the monitor.
+MARKER_CASE="$PROBE_DIR/marker-case"
+MARKER_ROOT_DIR="$MARKER_CASE/data"
+MARKER_HOME="$MARKER_CASE/home"
+MARKER_SEEN="$MARKER_CASE/target-stdin"
+MARKER_DIR="$MARKER_ROOT_DIR/guard-launch-monitor"
+mkdir -p "$MARKER_ROOT_DIR" "$MARKER_HOME" "$MARKER_CASE/tmp"
+MARKER_TARGET="$FIXTURE_ROOT/skills/clean/scripts/marker_target.py"
+{
+ printf 'import pathlib, sys\n'
+ # Byte-for-byte, through the binary buffer: the payload reaches python over a
+ # here-string now, and a decoded round trip would hide a newline difference.
+ printf 'pathlib.Path(sys.argv[1]).write_bytes(sys.stdin.buffer.read())\n'
+ printf 'pathlib.Path(sys.argv[1] + ".argv").write_text("\\n".join(sys.argv[3:]))\n'
+ printf 'raise SystemExit(int(sys.argv[2]))\n'
+} >"$MARKER_TARGET"
+
+MARKER_PAYLOAD=""
+# Echo the launcher's exit code; `$MARKER_SEEN` exists afterwards only if the
+# target actually started, and holds the bytes python read.
+marker_launch() {
+ local flag="$1" subdir="$2" target_rc="$3" rc=0
+ rm -f "$MARKER_SEEN" "$MARKER_SEEN.argv"
+ printf '%s' "$MARKER_PAYLOAD" |
+ HOME="$MARKER_HOME" TMPDIR="$MARKER_CASE/tmp" \
+ bash "$FIXTURE_ROOT/hooks/run-python-hook.sh" \
+ --marker-root "$MARKER_ROOT_DIR" "$flag" "$subdir" \
+ "$MARKER_TARGET" "$MARKER_SEEN" "$target_rc" >/dev/null 2>&1 || rc=$?
+ printf '%s' "$rc"
+}
+
+marker_state() {
+ [[ -f "$MARKER_DIR/$1.launched" ]] && printf 'present' || printf 'absent'
+}
+
+target_state() {
+ [[ -e "$MARKER_SEEN" ]] && printf 'ran' || printf 'skipped'
+}
+
+MARKER_PAYLOAD='{"session_id":"sess-1","hook_event_name":"PreToolUse"}'
+rm -rf "$MARKER_DIR"
+marker_rc="$(marker_launch --launch-marker guard-launch-monitor 0)"
+assert_eq "a guarded launch records the session" "present" "$(marker_state sess-1)"
+assert_eq "a guarded launch still runs its target" "ran" "$(target_state)"
+assert_eq "a guarded launch still reports its target's exit code" "0" "$marker_rc"
+assert_eq "the marker flags never reach python's argv" "" "$(cat "$MARKER_SEEN.argv")"
+
+# The monitor exists to catch a guard that launched and died, so a non-zero exit
+# must leave the marker that keeps the monitor running for the rest of the turn.
+rm -rf "$MARKER_DIR"
+marker_rc="$(marker_launch --launch-marker guard-launch-monitor 3)"
+assert_eq "a launch whose python exits non-zero still records the session" \
+ "present" "$(marker_state sess-1)"
+assert_eq "a launch whose python exits non-zero propagates that code" "3" "$marker_rc"
+
+# An EXISTING marker tree that holds no marker for this session is the shape a
+# launch-free session leaves, and is the only shape that may skip.
+MARKER_PAYLOAD='{"session_id":"sess-1","hook_event_name":"Stop"}'
+rm -rf "$MARKER_DIR"
+mkdir -p "$MARKER_DIR"
+marker_rc="$(marker_launch --skip-unless-marker guard-launch-monitor 0)"
+assert_eq "an unrecorded session exits 0" "0" "$marker_rc"
+assert_eq "an unrecorded session starts no python" "skipped" "$(target_state)"
+
+mkdir -p "$MARKER_DIR"
+: >"$MARKER_DIR/sess-1.launched"
+marker_rc="$(marker_launch --skip-unless-marker guard-launch-monitor 0)"
+assert_eq "a recorded session runs python" "ran" "$(target_state)"
+assert_eq "a recorded session exits 0" "0" "$marker_rc"
+
+# Byte identity across the buffer-and-replay: python receives what it would have
+# read from the inherited stdin, plus the newline `<<<` appends. Both consumers
+# (`json.load(sys.stdin)`, `sys.stdin.read()` then `json.loads`) ignore it.
+MARKER_EXPECTED="$MARKER_CASE/expected"
+printf '%s\n' "$MARKER_PAYLOAD" >"$MARKER_EXPECTED"
+assert_eq "the buffered payload reaches python unchanged" "same" \
+ "$(cmp -s "$MARKER_EXPECTED" "$MARKER_SEEN" && printf 'same' || printf 'differs')"
+
+# A payload the launcher cannot key on must behave exactly as it did before
+# these flags existed: record nothing, skip nothing, run python.
+MARKER_PAYLOAD='{"hook_event_name":"Stop","cwd":"/tmp"}'
+rm -rf "$MARKER_DIR"
+marker_rc="$(marker_launch --skip-unless-marker guard-launch-monitor 0)"
+assert_eq "a payload with no session id still runs python" "ran" "$(target_state)"
+assert_eq "a payload with no session id exits 0" "0" "$marker_rc"
+marker_launch --launch-marker guard-launch-monitor 0 >/dev/null
+assert_eq "a payload with no session id records nothing" "absent" \
+ "$([[ -d "$MARKER_DIR" ]] && printf 'present' || printf 'absent')"
+
+# A payload whose opening key is something else still keys correctly, through
+# the unanchored fallback: the docs show `session_id` first for every event but
+# guarantee no ordering, and a reordered payload must degrade to running python,
+# not to keying on nothing.
+MARKER_PAYLOAD='{"hook_event_name":"Stop","session_id":"sess-2"}'
+rm -rf "$MARKER_DIR"
+mkdir -p "$MARKER_DIR"
+marker_launch --skip-unless-marker guard-launch-monitor 0 >/dev/null
+assert_eq "a reordered payload still keys on its session id" "skipped" "$(target_state)"
+mkdir -p "$MARKER_DIR"
+: >"$MARKER_DIR/sess-2.launched"
+marker_launch --skip-unless-marker guard-launch-monitor 0 >/dev/null
+assert_eq "a reordered payload finds its own marker" "ran" "$(target_state)"
+
+# --- a launch that can write no marker at all must not silence the monitor ---
+#
+# Both candidates fail on the launch side here, which leaves the Stop row
+# nothing to find. Reading only the marker FILE would then skip every Stop for
+# the rest of the session: a silent failure in the one detector that exists to
+# report silent failures. The Stop row therefore skips only when a candidate
+# DIRECTORY exists, and otherwise runs the monitor as it did before the flags.
+#
+# "Unwritable" is staged structurally rather than through permission bits, which
+# MSYS cannot set against Windows ACLs: `mkdir -p` refuses a parent that is a
+# regular FILE on every host, and refuses a target that already exists as one.
+# The marker root sits under a file; `TMPDIR` itself stays a real directory
+# (bash may place a here-string there) with a file occupying the name the tmp
+# fallback wants.
+BLOCKED_CASE="$PROBE_DIR/blocked-case"
+BLOCKED_TMP="$BLOCKED_CASE/tmp"
+mkdir -p "$BLOCKED_CASE" "$BLOCKED_TMP"
+BLOCKED_FILE="$BLOCKED_CASE/not-a-directory"
+: >"$BLOCKED_FILE"
+: >"$BLOCKED_TMP/disk-hygiene-guard-launch-monitor"
+BLOCKED_ROOT="$BLOCKED_FILE/data"
+
+blocked_launch() {
+ local flag="$1" subdir="$2" rc=0
+ rm -f "$MARKER_SEEN" "$MARKER_SEEN.argv"
+ printf '%s' "$MARKER_PAYLOAD" |
+ HOME="$MARKER_HOME" TMPDIR="$BLOCKED_TMP" \
+ bash "$FIXTURE_ROOT/hooks/run-python-hook.sh" \
+ --marker-root "$BLOCKED_ROOT" "$flag" "$subdir" \
+ "$MARKER_TARGET" "$MARKER_SEEN" 0 >/dev/null 2>&1 || rc=$?
+ printf '%s' "$rc"
+}
+
+MARKER_PAYLOAD='{"session_id":"sess-3","hook_event_name":"PreToolUse"}'
+blocked_rc="$(blocked_launch --launch-marker guard-launch-monitor)"
+assert_eq "a launch that can write no marker still runs its target" \
+ "ran" "$(target_state)"
+assert_eq "a launch that can write no marker reports its target's code" \
+ "0" "$blocked_rc"
+assert_eq "a failed marker write creates no candidate directory" "absent" \
+ "$([[ -d "$BLOCKED_ROOT" || -d "$BLOCKED_TMP/disk-hygiene-guard-launch-monitor" ]] &&
+ printf 'present' || printf 'absent')"
+assert_eq "and it leaves the blocking file alone" "file" \
+ "$([[ -f "$BLOCKED_FILE" ]] && printf 'file' || printf 'gone')"
+
+MARKER_PAYLOAD='{"session_id":"sess-3","hook_event_name":"Stop"}'
+blocked_rc="$(blocked_launch --skip-unless-marker guard-launch-monitor)"
+assert_eq "a Stop with no candidate marker directory runs the monitor" \
+ "ran" "$(target_state)"
+assert_eq "a Stop with no candidate marker directory exits 0" "0" "$blocked_rc"
+
+# --- both wirings carry the flag the other one depends on ---
+guard_rows="$(jq '[.hooks.PreToolUse[].hooks[] |
+ select(.command | contains("destructive_guard.py"))] | length' "$HOOKS_JSON")"
+guard_marked="$(jq '[.hooks.PreToolUse[].hooks[] |
+ select(.command | contains("destructive_guard.py")) |
+ select(.command | contains("--launch-marker guard-launch-monitor"))] | length' \
+ "$HOOKS_JSON")"
+assert_eq "every engine-gate row records the session it launched in" \
+ "$guard_rows" "$guard_marked"
+assert_eq "the Stop row skips a session that launched no guard" "1" \
+ "$(jq '[.hooks.Stop[].hooks[] |
+ select(.command | contains("--skip-unless-marker guard-launch-monitor"))] | length' \
+ "$HOOKS_JSON")"
+
pass "all run-python-hook contract checks"