Prompt:
<task-notification>
<task-id>a1a1276a8cc7119e2</task-id>
<tool-use-id>toolu_0179DMxJWEdBpiL3o4fBcadg</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/a1a1276a8cc7119e2.output</output-file>
<status>completed</status>
<summary>Agent "Check Fallback docs vs script headers" completed</summary>
<result>Based on my analysis of the documentation and script headers, I found **no discrepancies**. Here's what I verified:

**Documentation vs. Script Headers Match:**

1. **README-Fallback.md** describes the system as:
   - "Mutual automatic fallback between two independent unRAID servers"
   - States machine with NORMAL, FALLBACK, NO_INTERNET, and DARK
   - Tiered fallback (Tier 1 immediate, Tier 2 at 4hr, Tier 3 at 12hr, Tier 4 at 24hr)
   - Handback sequence: strike confirmation → pre-flight → staged reverse → DDNS handoff → Tier 1 → start DDNS → NORMAL
   - Two scripts: `fallback.sh` (continuous) and `fallback_test.sh` (test harness)

   **fallback.sh header confirms all of this:**
   - "Each cycle (every FALLBACK_CHECK_INTERVAL seconds): 1. Ping remote 2. Ping internet"
   - States: NORMAL, FALLBACK, NO_INTERNET, DARK (lines 30-33)
   - Tier delays match: "Tier 2 — after HOST*_TIER2_DELAY (default 4hr)" through "Tier 4 — after HOST*_TIER4_DELAY (default 24hr)" (lines 37-39)
   - Handback steps 1-7 match exactly (lines 41-49)

2. **Manual-Fallback.md** specifies:
   - Config vars like `FALLBACK_ENABLED`, `FALLBACK_CHECK_INTERVAL=30`, `FALLBACK_HANDBACK_STRIKES=3`
   - `FALLBACK_TEST_BLOCK_WAIT=60` and `FALLBACK_TEST_HANDBACK_WAIT=300`

   **Both script headers confirm these:**
   - fallback.sh docs reference the same config variables in context
   - fallback_test.sh header explicitly states (lines 77-86): "FALLBACK_TEST_BLOCK_WAIT... (default: 60)" and "FALLBACK_TEST_HANDBACK_WAIT... (default: 300)"

3. **Script behaviors align:**
   - fallback.sh: "started at array start by array_started.sh" — README says "Continuously (started by `array_started.sh`)"
   - fallback_test.sh: "Controlled simulation... Contains no fallback logic" — README says "`fallback_test.sh` contains no fallback logic. It exercises the real `fallback.sh`"

**Conclusion:** All documentation accurately reflects the current script headers. No updates needed.</result>
<usage><subagent_tokens>22861</subagent_tokens><tool_uses>4</tool_uses><duration_ms>10900</duration_ms></usage>
</task-notification>


Prompt:
<task-notification>
<task-id>ad978912a994b547b</task-id>
<tool-use-id>toolu_01HuM27kN2WrSyy7WP6RqZXS</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/ad978912a994b547b.output</output-file>
<status>completed</status>
<summary>Agent "Check Partnership docs vs script headers" completed</summary>
<result>Based on my thorough review of the documentation and script headers, here are the discrepancies I found:

---

## DISCREPANCIES REPORT

**README-Partnership.md (line 143) vs partnership_onboard.sh header:**
README lists only 4 scripts in the table; missing `partnership_transfer.sh` and `onboard_cancel.sh`. Header for onboard mentions "Steps 5-6" for arr and "Step 6" for services, but the README doesn't document the services stack at all. The README's operational model diagram shows only 6 onboard steps on owner side, but the script header lists 10 steps including services stack deployment and conf push.

**README-Partnership.md (line 110-111) vs partnership_manager.sh and partnership_offboard.sh headers:**
README claims `partnership_offboard.sh` is "Called by `partnership_manager.sh --offboard` but runnable directly" and Manual (line 412) says "Called automatically by partnership_manager.sh --offboard. Can also be run directly." However, the actual script headers and structure show that offboard holds its own lock and is exec'd by the manager. The description is accurate but could be clearer about the lock ownership.

**Manual-Partnership.md (line 143) vs partnership_onboard.sh header:**
Manual lists `partnership_transfer.sh` in the script table with "When It Runs: Via partnership_manager.sh --offboard; or directly" but `partnership_transfer.sh` is NOT listed under "When It Runs" column at all — this appears to be a copy-paste error where offboard text is used instead. The transfer script is a separate script with its own entry needed.

**Manual-Partnership.md (line 379-393) vs actual scripts:**
Manual describes partnership_onboard.sh output tiers with "Without `--log`, step headers, per-step result lines (deployed/failed counts)..." but the script header (lines 14-32) describes 10 steps including a services stack deploy (Step 6-7) that isn't mentioned in the README's operational model (which only shows 8 steps).

**Manual-Partnership.md (line 145 - Missing Script) vs filesystem:**
The README script table is missing `onboard_cancel.sh` (labeled "Delete Keys" in actual script at line 3 of onboard_cancel.sh). This script exists and is executable but has no entry in the documentation table.

**README-Partnership.md (line 96-106) vs partnership_onboard.sh header:**
README says onboard includes "arr bootstrap" and "stops mirror arr containers," but doesn't mention the services stack (Emby, Jellyfin, Seerr). The script header shows Steps 4-7 include arr AND services stacks, but docs only reference arr.

---

**Summary:** The documentation is missing the services stack entirely from the operational model and overview, missing `onboard_cancel.sh`/`partnership_transfer.sh` from script tables, and the onboard step counts don't align (8 in README diagram vs 10 in script header). All script headers are internally consistent with their actual functionality; the docs lag behind implementation.</result>
<usage><subagent_tokens>30693</subagent_tokens><tool_uses>9</tool_uses><duration_ms>21991</duration_ms></usage>
</task-notification>


Prompt:
<task-notification>
<task-id>af3b18ad7c6bf3196</task-id>
<tool-use-id>toolu_019S4nyCRs1XtUeft3LW4vr4</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/af3b18ad7c6bf3196.output</output-file>
<status>completed</status>
<summary>Agent "Check Rsync docs vs script headers" completed</summary>
<result>Perfect. Now I have all the information I need. Let me analyze the discrepancies between the documentation and the script header.

Based on my reading of the files, here are the discrepancies I found:

---

## Discrepancies Report

**README-Rsync.md (line 71) vs rsync.sh header (line 71):**
README states: "Cron (direct): `*/30 * * * *` ──────────────────────────────────► `rsync.sh --profile=emby-fallback`"
However, the script header and Manual do not specify this schedule. The script itself shows no knowledge of being automatically scheduled every 30 minutes — that's purely an orchestrator responsibility. This is misleading as rsync.sh doesn't self-schedule; the cron entry would need to be external.

**Manual-Rsync.md (line 448) vs actual cron schedule context:**
Manual lists: "`*/30 * * * *   critical_sync_maintenance.sh`"
But the README line 71 shows emby-fallback is the 30-minute sync. This is a documentation inconsistency — the */30 entry should be for emby-fallback dirty sync, not critical_sync_maintenance. (Manual later shows line 448 lists critical as */30, but elsewhere describes emby-fallback as "every 30 minutes.")

**No discrepancy found regarding:**
- Profiles and their container definitions — all match
- Configuration variables — all match
- Script behavior description — matches accurately
- Pre-flight checks — all described correctly
- Bandwidth logging — matches rsync.sh implementation

The main issue is the 30-minute schedule attribution is ambiguous between critical_sync_maintenance.sh (line 448 of Manual) and the emby-fallback profile (implied by README line 71 and Manual line 50).</result>
<usage><subagent_tokens>21944</subagent_tokens><tool_uses>4</tool_uses><duration_ms>12783</duration_ms></usage>
</task-notification>


Prompt:
<task-notification>
<task-id>ade3db125039722eb</task-id>
<tool-use-id>toolu_01YbKi3pDTetbH21Me3uuosE</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/ade3db125039722eb.output</output-file>
<status>completed</status>
<summary>Agent "Check Media docs vs script headers" completed</summary>
<result>Based on my thorough review of the documentation and script headers, I've found the following discrepancies:

## Discrepancy Report

**1. permissions_repair.sh — Script does not exist**
- README-Media.md line 182 lists `media_shares_permissions.sh` in the script table.
- Manual-Media.md line 565 documents `media_shares_permissions.sh` under "FLAG REFERENCE".
- The actual script at line 1 is named `media_shares_permissions.sh`, not `permissions_repair.sh` as requested in the initial file list.

**2. arr_cleanup.sh vs. individual cleanup scripts**
- README-Media.md line 180 lists an `arr_cleanup.sh` script in the table.
- However, no such script exists. The actual scripts are `lidarr_cleanup.sh`, `sonarr_cleanup.sh`, and `radarr_cleanup.sh`.
- The docs should clarify these are three separate scripts, not one generic `arr_cleanup.sh`.

**3. play_state_sync.sh — Missing mention of --full flag**
- Manual-Media.md does not list `--full` mode in the "FLAG REFERENCE" section for `play_state_sync.sh`.
- The script header at line 65-66 documents: `play_state_sync.sh --full — Ignore PLAY_SYNC_DAYS — sync all played items (may be slow).`
- This flag should be added to Manual-Media.md documentation.

**4. playback_aware_sonarr_discovery.sh — Typo in script header**
- lidarr_cleanup.sh header line 91 contains: "Use when Soularr has filled the gaps"
- This appears to be a typo (should be "Sonarr" but in a Lidarr cleanup script, this text is out of context and mentions the wrong application).

**5. Lidarr cleanup safety layer count discrepancy**
- Manual-Media.md line 37 states: "Six gates — ALL must pass before any file is touched"
- lidarr_cleanup.sh header line 33 states: "Seven gates — ALL must pass before any file is touched"
- The Lidarr script correctly has 7 gates (lines 34-40), but the manual incorrectly documents 6 gates. Radarr and Sonarr both have 6 gates, which matches their documentation.

All other scripts match their documentation accurately regarding purpose, configuration variables, runtime modes, and operational safeguards.</result>
<usage><subagent_tokens>38010</subagent_tokens><tool_uses>14</tool_uses><duration_ms>17744</duration_ms></usage>
</task-notification>


lets use the updated headers and check the read mes and manuals to see if corrections are needed


Monitor media server sync and backup health


<task-notification>
<task-id>afaf88457841902a3</task-id>
<tool-use-id>toolu_01P5qBBRuixBMdWcWGzYdwPf</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/afaf88457841902a3.output</output-file>
<status>completed</status>
<summary>Agent "Check Transcodes docs vs script headers" completed</summary>
<result>Perfect! I found the discrepancy. Now let me verify all script headers are consistent with "every 7 minutes" as mentioned in Manual-Transcoding.md:

Based on my analysis of the documentation and script headers, I've identified one critical discrepancy:

**DISCREPANCY:**

**README-Transcoding.md (lines 135-136)** states:
- `transcode_cleanup.sh`: "Every **3 min** via transcode_management.sh — runs first"
- `transcode_manager.sh`: "Every **3 min** via transcode_management.sh — runs second"

However, the **same file (lines 158, 99)** and **Manual-Transcoding.md (lines 29, 33, 158, 221, 408)** consistently state both scripts run **"Every 7 minutes"**.

The script headers (transcode_cleanup.sh lines 9-10, transcode_manager.sh lines 9) also confirm **7 minutes** ("Called by transcode_management.sh (Orchestrators/) every 7 minutes").

**Status of other elements:**
- All four script headers accurately describe their purpose and operation
- Configuration variables mentioned in docs match what's listed in script headers
- Runtime modes (`--dry-run`, `--status`, `--log`) are consistent across both docs and headers
- Safety checks, state files, and operational safeguards are properly documented in headers
- No missing scripts — README documents three scripts; only three exist (ramdisk_stop.sh is **not** mentioned in docs and does **not** exist)

**Summary:** One scheduling discrepancy — README table contradicts itself by saying "Every 3 min" when all other documentation and script headers say "Every 7 minutes."</result>
<usage><subagent_tokens>26221</subagent_tokens><tool_uses>11</tool_uses><duration_ms>18844</duration_ms></usage>
</task-notification>


<task-notification>
<task-id>a5b2e8404449f1b7a</task-id>
<tool-use-id>toolu_012xPVMgTUbDALW14x3mXqPH</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/a5b2e8404449f1b7a.output</output-file>
<status>completed</status>
<summary>Agent "Check Tools docs vs script headers" completed</summary>
<result>Now I've gathered all the information needed. Let me compile the discrepancies:

## REPORT: Documentation vs Script Header Discrepancies

**1. claude_startup.sh — Runtime Mode Documentation**

**README-Tools.md says:** "After each unRAID reboot, or called by array_started.sh"

**Manual-Tools.md says:**
- `claude_startup.sh` → "set up persistent symlinks only — default, used by array_started.sh on boot"
- `claude_startup.sh --launch` → "Set up persistent symlinks and launch Claude interactively"

**Script header says:**
- `claude_startup.sh` → "Set up persistent symlinks only — default, used by array_started.sh on boot"
- `claude_startup.sh --launch` → "Set up persistent symlinks and launch Claude interactively"

**Issue:** Manual documents `--setup` flag (in the "Calling from array_started.sh" section) but the script header and actual code only support `--launch`. The manual states: "To auto-restore Claude data on every boot without launching an interactive session: `/path/to/Tools/claude_startup.sh --setup`" but the script only recognizes `--launch`. Default behavior (no flag) sets up symlinks; `--launch` adds the interactive launch.

**2. fallback_state_reset.sh — Built Status**

**Manual-Tools.md line 14 says:** "*(not yet built — manual workaround)*"

**Script header (lines 1-25) clearly shows:** A fully functional script with PURPOSE, OPERATIONAL SAFEGUARDS, and RUNTIME MODES documented.

**Issue:** The manual outdated — script is built and complete, not "not yet built."

**3. docker_prune_images.sh — Missing from Documentation**

**Script exists:** `/boot/config/plugins/varaverk/Tools/docker_prune_images.sh` is fully implemented with PURPOSE, RUNTIME MODES.

**README-Tools.md and Manual-Tools.md:** No mention of this script anywhere in the scripts table or contents.

**Issue:** `docker_prune_images.sh` exists and is functional but is not documented.

**4. bandwidth_test.sh — File Not Found**

**README-Tools.md line 128 references:** "`ramdisk_stop.sh`" (not bandwidth_test.sh)

**Manual-Tools.md references:** "`ramdisk_stop.sh`" (not bandwidth_test.sh)

**Requested file:** `bandwidth_test.sh` does not exist in Tools/ directory.

**Issue:** No discrepancy here — the requested file doesn't exist and the documentation correctly references `ramdisk_stop.sh`.</result>
<usage><subagent_tokens>28615</subagent_tokens><tool_uses>11</tool_uses><duration_ms>20538</duration_ms></usage>
</task-notification>


<task-notification>
<task-id>aad2d35c7ddc0732d</task-id>
<tool-use-id>toolu_014vuBo3eiJ6aTYQbYJxrVcX</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/aad2d35c7ddc0732d.output</output-file>
<status>completed</status>
<summary>Agent "Check Kernel and Plugin docs vs script headers" completed</summary>
<result>Now let me cross-check the documentation claims against the script headers for any discrepancies.

Based on my reading of the documentation files and script headers, here are the findings:

## Discrepancy Report

**DOCUMENTATION vs SCRIPT HEADER MATCHES:**

All documentation statements match the script headers. Specifically:

1. **README-Kernel.md** correctly describes `decision_engine.sh` as "Behavior-driven scoring kernel" with "scoring, decay, deduplication, threshold evaluation" — matches the header's purpose statement and design principles (lines 3-40).

2. **README-Plugin.md** correctly lists scripts:
   - `dev_install.sh` — "One-time developer setup: symlinks plugin into web server" — matches header's purpose (lines 6-14).
   - The README notes no other `.sh` files in `Plugin/` root directory, which is accurate (only `dev_install.sh` and `build.sh` exist).

3. **Manual-Plugin.md** correctly describes:
   - `dev_install.sh` installation process (section "FIRST-TIME INSTALL", lines 19-37) — matches script behavior.
   - `.plg` setup procedure (section "PLG SETUP", lines 40-71) — matches script headers.
   - SCRIPTS_DIR configuration (section "SCRIPTS DIRECTORY SETTING", lines 74-89) — aligns with `varaverk.cfg` mentioned in README-Plugin.md.

4. **README-Plugin.md** mentions `build.sh` in the folder structure but does NOT list it in the "SCRIPTS IN THIS FOLDER" table (which only includes `dev_install.sh`). The actual `build.sh` header exists with clear purpose: packaging the plugin as a `.txz` for release (lines 6-27 of build.sh).

**ONE MINOR OMISSION (not a discrepancy):**

The README-Plugin.md "SCRIPTS IN THIS FOLDER" table (lines 76-78) documents only `dev_install.sh` and omits `build.sh`. However, the folder structure diagram earlier (lines 32-53) does not list build.sh either, so this is consistent within the documentation — just incomplete. The script itself is present and functional.

**CONCLUSION:** No actual discrepancies between documentation and script headers. The documentation accurately reflects the scripts' purposes, configurations, and behaviors.</result>
<usage><subagent_tokens>17742</subagent_tokens><tool_uses>8</tool_uses><duration_ms>15201</duration_ms></usage>
</task-notification>


<task-notification>
<task-id>a9329c643d0f9b817</task-id>
<tool-use-id>toolu_0114YPA8vYaeU3tyn1Qi7jLj</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/a9329c643d0f9b817.output</output-file>
<status>completed</status>
<summary>Agent "Check System_Essentials and Orchestrators docs vs script headers" completed</summary>
<result>Perfect! Now I have all the information I need. Let me analyze the discrepancies:

## REPORT: Documentation vs Script Header Discrepancies

Based on a thorough review of the README, Manual, and script headers, I found the following discrepancies:

**CRITICAL LOCATION MISMATCH:**

1. **README-Orchestrators.md (line 175):** Documents `arrs_failed_stalled_recovery.sh` as running "Every 6 hours" under the Orchestrators section. However, the actual script header shows "Recommended schedule: 0 5 * * * (5am daily) Or every 6hr: 0 */6 * * *" - it's optional per-schedule, and more importantly, **the script lives in `/boot/config/plugins/varaverk/Media/` not Orchestrators/**. The README tables it as an orchestrator when it's actually a standalone script typically called by an orchestrator.

**SCHEDULE DISCREPANCIES:**

2. **README-Orchestrators.md (line 109):** States intermediate_sync_maintenance.sh runs "0 */4 * * *" (every 4 hours). However, the README header at line 6 says "Schedule: 0 */4 * * * (every 4 hours)" — this matches and is correct.

3. **README-Orchestrators.md (lines 105-112):** Lists "arrs_failed_stalled_recovery.sh" in the cron schedule section as "Every 6 hours" but this script is NOT an orchestrator and should NOT be in this list. It's orchestrated by being called FROM orchestrators as needed, not scheduled directly.

**CONTENT ACCURACY ISSUES:**

4. **README-Orchestrators.md (line 178):** States arrs_failed_stalled_recovery.sh "runs every 6 hours" in the table. The actual script header (Media/arrs_failed_stalled_recovery.sh line 81-82) shows TWO possible schedules: "5am daily" OR "every 6hr" — it's flexible and depends on configuration, not fixed at 6 hours.

5. **README-Orchestrators.md (line 109-112):** The "COMPLETE SCHEDULE" section includes arrs_failed_stalled_recovery.sh as "0 */6 * * *" but this contradicts the fact that it's shown as callable from orchestrators (line 175 references it under the arrs_failed_stalled_recovery section in its own dedicated area, not as part of the main orchestrator list).

**SUMMARY OF ISSUES:**
- arrs_failed_stalled_recovery.sh is documented as an orchestrator when it's actually a Media script
- Schedule flexibility (5am vs every 6hr) is documented as fixed "every 6 hours"
- Script location mismatch between documentation layout and actual filesystem

All other scripts match their documentation correctly regarding schedules, execution order, and purposes.</result>
<usage><subagent_tokens>52317</subagent_tokens><tool_uses>15</tool_uses><duration_ms>24143</duration_ms></usage>
</task-notification>


Still waiting on 5 agents (Transcodes, Tools, System_Essentials/Orchestrators, root README, Kernel/Plugin). Results so far are coming in well — collecting findings as they arrive.


All agents done except root README. Let me verify the key findings before fixing anything.


Bash

ls /boot/config/plugins/varaverk/Media/*.sh 2>/dev/null
echo "---"
ls /boot/config/plugins/varaverk/Tools/*.sh 2>/dev/null
echo "---"
ls /boot/config/plugins/varaverk/Docker_Essentials/*.sh 2>/dev/null

List scripts in Media, Tools, Docker_Essentials


/boot/config/plugins/varaverk/Media/arr_sync.sh*
/boot/config/plugins/varaverk/Media/arrs_failed_stalled_recovery.sh*
/boot/config/plugins/varaverk/Media/lidarr_cleanup.sh*
/boot/config/plugins/varaverk/Media/lidarr_missing_art.sh*
/boot/config/plugins/varaverk/Media/media_cleaner.sh*
/boot/config/plugins/varaverk/Media/media_shares_permissions.sh*
/boot/config/plugins/varaverk/Media/play_state_sync.sh*
/boot/config/plugins/varaverk/Media/playback_aware_lidarr_discovery.sh*
/boot/config/plugins/varaverk/Media/playback_aware_radarr_discovery.sh*
/boot/config/plugins/varaverk/Media/playback_aware_sonarr_discovery.sh*
/boot/config/plugins/varaverk/Media/radarr_cleanup.sh*
/boot/config/plugins/varaverk/Media/radarr_tmdb_removed.sh*
/boot/config/plugins/varaverk/Media/sonarr_cleanup.sh*
/boot/config/plugins/varaverk/Media/sonarr_tvdb_removed.sh*
---
/boot/config/plugins/varaverk/Tools/bulk_permissions_repair.sh*
/boot/config/plugins/varaverk/Tools/claude_startup.sh*
/boot/config/plugins/varaverk/Tools/container_data_export.sh*
/boot/config/plugins/varaverk/Tools/docker_prune_images.sh*
/boot/config/plugins/varaverk/Tools/emby_database_repair.sh*
/boot/config/plugins/varaverk/Tools/emby_to_lidarr_sync.sh*
/boot/config/plugins/varaverk/Tools/emby_to_radarr_sync.sh*
/boot/config/plugins/varaverk/Tools/emby_to_sonarr_sync.sh*
/boot/config/plugins/varaverk/Tools/fallback_state_reset.sh*
/boot/config/plugins/varaverk/Tools/ramdisk_stop.sh*
/boot/config/plugins/varaverk/Tools/smart_long_test.sh*
/boot/config/plugins/varaverk/Tools/watchdog_skip_list_manager.sh*
/boot/config/plugins/varaverk/Tools/zfs_pool_scrub.sh*
---
/boot/config/plugins/varaverk/Docker_Essentials/docker_container_stop.sh*
/boot/config/plugins/varaverk/Docker_Essentials/docker_daily_restart.sh*
/boot/config/plugins/varaverk/Docker_Essentials/docker_network_connect.sh*
/boot/config/plugins/varaverk/Docker_Essentials/docker_update.sh*
/boot/config/plugins/varaverk/Docker_Essentials/docker_update_remaining.sh*
/boot/config/plugins/varaverk/Docker_Essentials/docker_weekly_restart.sh*
/boot/config/plugins/varaverk/Docker_Essentials/downloaders_reset.sh*


grep -n "STRIKE\|REBOOT_WINDOW\|SYS_WATCHDOG_MEM" /boot/config/plugins/varaverk/Watchdogs/stability_watchdog.sh | head -20

Check actual variable names in stability_watchdog.sh


42:#   Stops all containers not in SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED.
76:#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 2)
78:#   SYS_WATCHDOG_STRIKES            — consecutive failures before reboot (default: 3)
80:#   SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED — containers exempt from memory shutdown
128:SYS_WATCHDOG_REBOOT_WINDOW=$(( SYS_WATCHDOG_REBOOT_WINDOW_HRS * 3600 ))
141:log "$ICON_GEAR Config: strikes=${SYS_WATCHDOG_STRIKE_LIMIT} reboot-limit=${SYS_WATCHDOG_REBOOT_LIMIT}/${SYS_WATCHDOG_REBOOT_WINDOW_HRS}hr oom-limit=${SYS_WATCHDOG_OOM_LIMIT}"
142:log "$ICON_GEAR Tiers:  rootfs-crit=${SYS_WATCHDOG_ROOTFS_CRITICAL_PCT}% rootfs-warn=${SYS_WATCHDOG_ROOTFS_PCT}% ram-reboot=${SYS_WATCHDOG_MEM_GB}GB load=${SYS_WATCHDOG_LOAD_MULTIPLIER}x(${TOTAL_CORES}cores) cpu-temp=${SYS_WATCHDOG_CPU_TEMP_MAX}°C zombies=${SYS_WATCHDOG_ZOMBIE_LIMIT}"
160:    echo "$ICON_MEM    RAM critical:         < ${SYS_WATCHDOG_MEM_GB}GB"
167:    echo "$ICON_MEM    RAM reboot:           < ${SYS_WATCHDOG_MEM_GB}GB (+ strikes)"
173:    echo "$ICON_GEAR   Strike limit:         ${SYS_WATCHDOG_STRIKE_LIMIT} cycles"
175:    echo "$ICON_REBOOT_SMART Reboot limit:  ${SYS_WATCHDOG_REBOOT_LIMIT} in ${SYS_WATCHDOG_REBOOT_WINDOW_HRS}hr"
234:    cutoff=$(( now - SYS_WATCHDOG_REBOOT_WINDOW ))
330:# ── STANDARD STRIKE CHECK ─────────────────────────────────────────────────────────────────────
339:        warn "$description — strike $strikes/$SYS_WATCHDOG_STRIKE_LIMIT"
340:        if (( strikes >= SYS_WATCHDOG_STRIKE_LIMIT )); then
391:    log "Recent reboots in ${SYS_WATCHDOG_REBOOT_WINDOW_HRS}hr window: $RECENT_REBOOTS / $SYS_WATCHDOG_REBOOT_LIMIT"
610:        if [[ "$MEM_GB" -lt "$SYS_WATCHDOG_MEM_GB" ]]; then
752:        # Runaway uses SYS_WATCHDOG_RUNAWAY_STRIKES not global strike limit
755:            RLIMIT="${SYS_WATCHDOG_RUNAWAY_STRIKES:-3}"


grep -n "3 min\|7 min\|Every 3\|Every 7" /boot/config/plugins/varaverk/Transcodes/README-Transcoding.md

Find the 3 min vs 7 min discrepancy in Transcodes README


86:files — thousands of subprocess calls every 7 minutes.
119:                           (every 7 minutes — order non-negotiable)
135:| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 3 min via transcode_management.sh — runs first |
136:| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 3 min via transcode_management.sh — runs second |
158:Every 7 minutes (transcode_management.sh):


grep -n "SYS_WATCHDOG_STRIKE\|SYS_WATCHDOG_REBOOT_WINDOW\|SYS_WATCHDOG_MEM_GB\|SYS_WATCHDOG_MEM " /boot/config/plugins/varaverk/Watchdogs/stability_watchdog.sh | head -20

Check variable names used in stability_watchdog body


76:#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 2)
78:#   SYS_WATCHDOG_STRIKES            — consecutive failures before reboot (default: 3)
128:SYS_WATCHDOG_REBOOT_WINDOW=$(( SYS_WATCHDOG_REBOOT_WINDOW_HRS * 3600 ))
141:log "$ICON_GEAR Config: strikes=${SYS_WATCHDOG_STRIKE_LIMIT} reboot-limit=${SYS_WATCHDOG_REBOOT_LIMIT}/${SYS_WATCHDOG_REBOOT_WINDOW_HRS}hr oom-limit=${SYS_WATCHDOG_OOM_LIMIT}"
142:log "$ICON_GEAR Tiers:  rootfs-crit=${SYS_WATCHDOG_ROOTFS_CRITICAL_PCT}% rootfs-warn=${SYS_WATCHDOG_ROOTFS_PCT}% ram-reboot=${SYS_WATCHDOG_MEM_GB}GB load=${SYS_WATCHDOG_LOAD_MULTIPLIER}x(${TOTAL_CORES}cores) cpu-temp=${SYS_WATCHDOG_CPU_TEMP_MAX}°C zombies=${SYS_WATCHDOG_ZOMBIE_LIMIT}"
160:    echo "$ICON_MEM    RAM critical:         < ${SYS_WATCHDOG_MEM_GB}GB"
167:    echo "$ICON_MEM    RAM reboot:           < ${SYS_WATCHDOG_MEM_GB}GB (+ strikes)"
173:    echo "$ICON_GEAR   Strike limit:         ${SYS_WATCHDOG_STRIKE_LIMIT} cycles"
175:    echo "$ICON_REBOOT_SMART Reboot limit:  ${SYS_WATCHDOG_REBOOT_LIMIT} in ${SYS_WATCHDOG_REBOOT_WINDOW_HRS}hr"
234:    cutoff=$(( now - SYS_WATCHDOG_REBOOT_WINDOW ))
339:        warn "$description — strike $strikes/$SYS_WATCHDOG_STRIKE_LIMIT"
340:        if (( strikes >= SYS_WATCHDOG_STRIKE_LIMIT )); then
391:    log "Recent reboots in ${SYS_WATCHDOG_REBOOT_WINDOW_HRS}hr window: $RECENT_REBOOTS / $SYS_WATCHDOG_REBOOT_LIMIT"
610:        if [[ "$MEM_GB" -lt "$SYS_WATCHDOG_MEM_GB" ]]; then


grep -n "arr_cleanup\|arrs_failed\|lidarr_cleanup\|sonarr_cleanup\|radarr_cleanup" /boot/config/plugins/varaverk/Media/README-Media.md | head -20

Check what the Media README says about cleanup scripts


77:`lidarr_cleanup.sh`, `sonarr_cleanup.sh`, `radarr_cleanup.sh` — API-verified orphan
88:`arrs_failed_stalled_recovery.sh` — detect and recover failed imports and stalled
116:4. lidarr_cleanup.sh             — after permissions + clean
117:5. sonarr_cleanup.sh
118:6. radarr_cleanup.sh
130:arrs_failed_stalled_recovery.sh
159:cleaned by arr_cleanup on all nodes after the next sync cycle.
185:| `lidarr_cleanup.sh` | Delete orphaned music files not tracked by Lidarr | Daily via `media_management.sh` |
186:| `sonarr_cleanup.sh` | Delete orphaned TV files not tracked by Sonarr | Daily via `media_management.sh` |
187:| `radarr_cleanup.sh` | Delete orphaned movie files not tracked by Radarr | Daily via `media_management.sh` |
188:| `arrs_failed_stalled_recovery.sh` | Auto-recover failed imports and stalled downloads | Daily (5am or every 6hr) |
206:        └── then arr_cleanup removes orphans on all nodes (old paths, removed content)
215:  lidarr_cleanup.sh ──────────── queries Lidarr API → walks /Music → deletes orphans
216:  sonarr_cleanup.sh ──────────── queries Sonarr API → walks /Tv_Shows → deletes orphans
217:  radarr_cleanup.sh ──────────── queries Radarr API → walks /Movies → deletes orphans
222:  arrs_failed_stalled_recovery.sh ── importFailed/stalled → blocklist → re-search


grep -n "arrs_failed\|every 6\|Every 6\|6 hour\|6hr" /boot/config/plugins/varaverk/Orchestrators/README-Orchestrators.md | head -15

Check how arrs_failed_stalled_recovery is documented in orchestrators


74:The fix: `arrs_failed_stalled_recovery.sh` runs every 6 hours. It finds all
175:| `arrs_failed_stalled_recovery.sh` | Failed import + stalled download recovery | Every 6 hours |
385:## 🔄  arrs_failed_stalled_recovery.sh
393:# Scheduled: 0 */6 * * *   (every 6 hours)
461:# importFailed may still resolve itself. Waiting 6 hours before intervening
495:arrs_failed_stalled_recovery.sh              # normal run
496:arrs_failed_stalled_recovery.sh --dry-run    # show what would be actioned, no changes
497:arrs_failed_stalled_recovery.sh --log        # verbose — show each queue item evaluated
498:arrs_failed_stalled_recovery.sh --status     # show arr config and API connectivity


<task-notification>
<task-id>ad9a317b416360985</task-id>
<tool-use-id>toolu_01V84phy1bV8zVPik2m81Bw9</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/ad9a317b416360985.output</output-file>
<status>completed</status>
<summary>Agent "Check root README and Manual vs project structure" completed</summary>
<result>So the intermediate_sync_maintenance.sh script header lists:
1. conf_sync
2. arr_sync
3. rsync
4. INTERMEDIATE_MAINTENANCE_SCRIPTS

It does NOT list `arrs_failed_stalled_recovery`. The README-Orchestrators.md comment is misleading/wrong!

Now let me generate my final report of discrepancies:

## REPORT: Documentation vs. Implementation Discrepancies

**DISCREPANCY 1: README-User_Script_Plug-in.md lists `intermediate_sync_maintenance` but it's NOT documented**
- FILE: README-User_Script_Plug-in.md
- **What the doc says:** The file contains extensive documentation for `arrs_failed_stalled_recovery` as a scheduled orchestrator (Schedule: 0 */6 * * *) at line 210, claiming it runs every 6 hours separately from other orchestrators.
- **What is actually true:** `arrs_failed_stalled_recovery.sh` exists in `Media/` folder but is NOT called by any orchestrator. The script exists but has no scheduled entry. Additionally, `intermediate_sync_maintenance.sh` is completely omitted from README-User_Script_Plug-in.md despite being a core orchestrator that runs every 4 hours (0 */4 * * *) per Manual.md line 321.

**DISCREPANCY 2: README-Orchestrators.md claims intermediate_sync includes "failed recovery"**
- FILE: README-Orchestrators.md (line 105)
- **What the doc says:** "0 */4 * * * intermediate_sync_maintenance.sh ← arr sync + failed recovery + optional rsync"
- **What is actually true:** The intermediate_sync_maintenance.sh script header (lines 8-12) documents execution order as: conf_sync → arr_sync → rsync → INTERMEDIATE_MAINTENANCE_SCRIPTS. It does NOT call arrs_failed_stalled_recovery.sh. Failed recovery is not included in this orchestrator.

**DISCREPANCY 3: Manual.md Schedule Table describes intermediate_sync incorrectly**
- FILE: Manual.md (line 321)
- **What the doc says:** "intermediate_sync_maintenance | 0 */4 * * * | arr sync + failed recovery"
- **What is actually true:** Same as Discrepancy 2 above—the script does not include failed recovery step. It calls arr_sync + optional rsync + maintenance scripts.

**Summary:** The relationship between `intermediate_sync_maintenance` (4-hour orchestrator) and `arrs_failed_stalled_recovery` (unused Media script) is documented inconsistently across three files. Two docs incorrectly claim failed recovery runs as part of the 4-hour cycle, and one doc completely omits the intermediate_sync_maintenance orchestrator while attempting to schedule the standalone Media script separately.</result>
<usage><subagent_tokens>66135</subagent_tokens><tool_uses>29</tool_uses><duration_ms>69822</duration_ms></usage>
</task-notification>


grep -n "REBOOT_WINDOW_HRS\|STRIKE_LIMIT\|SYS_WATCHDOG_STRIKES" /boot/config/plugins/varaverk/Watchdogs/Manual-Watchdogs.md | head -10

Check Manual-Watchdogs for conflicting values


grep -n "SYS_WATCHDOG_REBOOT_WINDOW_HRS" /boot/config/plugins/varaverk/Configurations/master.conf

Check actual default value in master.conf


321:WATCHDOG_APPDATA_STRIKE_LIMIT=3       # strikes before alert
448:NETWORK_WATCHDOG_NPM_STRIKE_LIMIT=2
531:**Tier 3 — STANDARD (`SYS_WATCHDOG_STRIKE_LIMIT` consecutive failures → reboot)**
575:SYS_WATCHDOG_REBOOT_WINDOW_HRS=2   # window in hours
672:WATCHDOG_APPDATA_STRIKE_LIMIT=3
684:SYS_WATCHDOG_STRIKE_LIMIT=2
686:SYS_WATCHDOG_REBOOT_WINDOW_HRS=12
716:NETWORK_WATCHDOG_NPM_STRIKE_LIMIT=2


1381:    SYS_WATCHDOG_REBOOT_WINDOW_HRS=12

1381:    SYS_WATCHDOG_REBOOT_WINDOW_HRS=12


head -60 /boot/config/plugins/varaverk/Tools/claude_startup.sh | grep -n "setup\|launch\|RUNTIME\|FLAG"

Check claude_startup.sh for --setup vs --launch


31:# RUNTIME MODES
37:# claude_startup.sh --launch
38:#     Set up persistent symlinks and launch Claude interactively.
43:[[ "$1" == "--launch" ]] && LAUNCH=true


Prompt:
<task-notification>
<task-id>ad9a317b416360985</task-id>
<tool-use-id>toolu_01V84phy1bV8zVPik2m81Bw9</tool-use-id>
<output-file>/tmp/claude-0/-root/685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f/tasks/ad9a317b416360985.output</output-file>
<status>completed</status>
<summary>Agent "Check root README and Manual vs project structure" completed</summary>
<result>So the intermediate_sync_maintenance.sh script header lists:
1. conf_sync
2. arr_sync
3. rsync
4. INTERMEDIATE_MAINTENANCE_SCRIPTS

It does NOT list `arrs_failed_stalled_recovery`. The README-Orchestrators.md comment is misleading/wrong!

Now let me generate my final report of discrepancies:

## REPORT: Documentation vs. Implementation Discrepancies

**DISCREPANCY 1: README-User_Script_Plug-in.md lists `intermediate_sync_maintenance` but it's NOT documented**
- FILE: README-User_Script_Plug-in.md
- **What the doc says:** The file contains extensive documentation for `arrs_failed_stalled_recovery` as a scheduled orchestrator (Schedule: 0 */6 * * *) at line 210, claiming it runs every 6 hours separately from other orchestrators.
- **What is actually true:** `arrs_failed_stalled_recovery.sh` exists in `Media/` folder but is NOT called by any orchestrator. The script exists but has no scheduled entry. Additionally, `intermediate_sync_maintenance.sh` is completely omitted from README-User_Script_Plug-in.md despite being a core orchestrator that runs every 4 hours (0 */4 * * *) per Manual.md line 321.

**DISCREPANCY 2: README-Orchestrators.md claims intermediate_sync includes "failed recovery"**
- FILE: README-Orchestrators.md (line 105)
- **What the doc says:** "0 */4 * * * intermediate_sync_maintenance.sh ← arr sync + failed recovery + optional rsync"
- **What is actually true:** The intermediate_sync_maintenance.sh script header (lines 8-12) documents execution order as: conf_sync → arr_sync → rsync → INTERMEDIATE_MAINTENANCE_SCRIPTS. It does NOT call arrs_failed_stalled_recovery.sh. Failed recovery is not included in this orchestrator.

**DISCREPANCY 3: Manual.md Schedule Table describes intermediate_sync incorrectly**
- FILE: Manual.md (line 321)
- **What the doc says:** "intermediate_sync_maintenance | 0 */4 * * * | arr sync + failed recovery"
- **What is actually true:** Same as Discrepancy 2 above—the script does not include failed recovery step. It calls arr_sync + optional rsync + maintenance scripts.

**Summary:** The relationship between `intermediate_sync_maintenance` (4-hour orchestrator) and `arrs_failed_stalled_recovery` (unused Media script) is documented inconsistently across three files. Two docs incorrectly claim failed recovery runs as part of the 4-hour cycle, and one doc completely omits the intermediate_sync_maintenance orchestrator while attempting to schedule the standalone Media script separately.</result>
<usage><subagent_tokens>66135</subagent_tokens><tool_uses>29</tool_uses><duration_ms>69822</duration_ms></usage>
</task-notification>


All agents complete. Let me check the last few facts before making fixes.


grep -n "INTERMEDIATE_MAINTENANCE\|arrs_failed" /boot/config/plugins/varaverk/Configurations/master.conf | head -10

Check if arrs_failed_stalled_recovery is in INTERMEDIATE_MAINTENANCE_SCRIPTS


356:    INTERMEDIATE_MAINTENANCE_SCRIPTS=(
357:        "Media/arrs_failed_stalled_recovery.sh"  # blocklist + re-search failed/stalled arr queue items


Check actual defaults for strike limit and reboot window


1376:    SYS_WATCHDOG_STRIKE_LIMIT=2         # consecutive failures before reboot trigger
1381:    SYS_WATCHDOG_REBOOT_WINDOW_HRS=12


grep -n "fallback_state_reset\|docker_prune\|setup\b" /boot/config/plugins/varaverk/Tools/README-Tools.md | head -15

Check what Tools README actually says about these scripts


9:> a risky operation, or during a one-time setup task. Having a dedicated folder keeps
21:Fix: `fallback_state_reset.sh` — shows current state, prompts for confirmation,
70:`fallback_state_reset.sh`, `watchdog_skip_list_manager.sh`
78:**Lifecycle Tools** — Backup, setup, and migration support
101:  fallback.sh ──────── writes FALLBACK_STATE_FILE ──► fallback_state_reset.sh reads/writes it
119:| `fallback_state_reset.sh` | State file stuck in FALLBACK after test or failed handback | After fallback testing or manual intervention |
127:| `claude_startup.sh` | Claude Code session setup after reboot — symlinks persistent storage | After each unRAID reboot, or called by array_started.sh |
128:| `ramdisk_stop.sh` | Safely stop the transcode ramdisk — redirect symlink to SSD, unmount, update state | Before re-running ramdisk_setup.sh with new size or thresholds |
129:| `emby_to_lidarr_sync.sh` | Add all Emby album artists not yet tracked in Lidarr | After Lidarr setup, database wipe, or when you suspect gaps |
130:| `emby_to_sonarr_sync.sh` | Add all Emby TV series not yet tracked in Sonarr | After Sonarr setup, database wipe, or when you suspect gaps |
131:| `emby_to_radarr_sync.sh` | Add all Emby movies not yet tracked in Radarr | After Radarr setup, database wipe, or when you suspect gaps |
146:│  fallback_state_reset.sh     ◄── after fallback test / failed handback│
152:│  recreate_shares             ◄── fresh HOST2 setup or disk rebuild    │
157:│  emby_to_lidarr_sync         ◄── Lidarr setup / database wipe / gap  │
158:│  emby_to_sonarr_sync         ◄── Sonarr setup / database wipe / gap  │


grep -n "fallback_state_reset\|not yet built\|docker_prune\|--setup" /boot/config/plugins/varaverk/Tools/Manual-Tools.md | head -20

Check Manual-Tools for specific claims about fallback_state_reset and --setup


14:- [fallback_state_reset.sh](#fallback_state_resetsh) *(not yet built — manual workaround)*
35:`--status`, fallback_state_reset current-state section) always show their state
144:## fallback_state_reset.sh
621:/path/to/Tools/claude_startup.sh --setup
628:claude_startup.sh --setup   # set up symlinks only — no launch (for array_started.sh)


grep -n "arrs_failed\|Every 6\|every 6\|4 hour\|4hr" /boot/config/plugins/varaverk/Orchestrators/README-Orchestrators.md | head -15

Check arrs_failed schedule claim in Orchestrators README


74:The fix: `arrs_failed_stalled_recovery.sh` runs every 6 hours. It finds all
175:| `arrs_failed_stalled_recovery.sh` | Failed import + stalled download recovery | Every 6 hours |
179:| `intermediate_sync_maintenance.sh` | arr library sync, artwork, failed recovery | Every 4 hours |
385:## 🔄  arrs_failed_stalled_recovery.sh
393:# Scheduled: 0 */6 * * *   (every 6 hours)
495:arrs_failed_stalled_recovery.sh              # normal run
496:arrs_failed_stalled_recovery.sh --dry-run    # show what would be actioned, no changes
497:arrs_failed_stalled_recovery.sh --log        # verbose — show each queue item evaluated
498:arrs_failed_stalled_recovery.sh --status     # show arr config and API connectivity
671:# Tier 4 fallback writeback (HOST1 returns after 24hr+ outage):
911:# Every 4 hours — arr library sync + failed import recovery + optional rsync:


grep -n "lidarr.*gate\|six gate\|seven gate\|Six gate\|Seven gate" /boot/config/plugins/varaverk/Media/Manual-Media.md | head -10

Check gate count for lidarr_cleanup in Manual-Media


(Bash completed with no output)


All 12 agents complete. Here's the consolidated finding list — let me now make the fixes, starting with the clear factual errors:


70	# CONFIGURATION
71	# ==============================================================================================
72	#
73	# master.conf — System Watchdog section
74	#   Full variable listing in master.conf. Key variables:
75	#
76	#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 2)
77	#   SYS_WATCHDOG_MAX_REBOOTS        — max reboots in window before giving up (default: 3)
78	#   SYS_WATCHDOG_STRIKES            — consecutive failures before reboot (default: 3)
79	#   SYS_WATCHDOG_OOM_LIMIT          — OOM kills/cycle to trigger URGENT bypass (default: 3)
80	#   SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED — containers exempt from memory shutdown
81	#
82	# ==============================================================================================
83	# STATE FILES
84	# ==============================================================================================
85	#
86	#   SYS_WATCHDOG_STATE_FILE  — strike counters and cycle state
87	#   SYS_WATCHDOG_REBOOT_LOG  — reboot history for rate limiting
88	#   SYS_WATCHDOG_OOM_FILE    — OOM kill counter from previous cycle
89	#

# CONFIGURATION
# ==============================================================================================
#
# master.conf — System Watchdog section
#   Full variable listing in master.conf. Key variables:
#
#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 2)
#   SYS_WATCHDOG_MAX_REBOOTS        — max reboots in window before giving up (default: 3)
#   SYS_WATCHDOG_STRIKES            — consecutive failures before reboot (default: 3)
#   SYS_WATCHDOG_OOM_LIMIT          — OOM kills/cycle to trigger URGENT bypass (default: 3)
#   SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED — containers exempt from memory shutdown
#
# ==============================================================================================
# STATE FILES
# ==============================================================================================
#
#   SYS_WATCHDOG_STATE_FILE  — strike counters and cycle state
#   SYS_WATCHDOG_REBOOT_LOG  — reboot history for rate limiting
#   SYS_WATCHDOG_OOM_FILE    — OOM kill counter from previous cycle
#


570	safety concerns.
571
572	### Reboot Rate Limit
573
574	```bash
575	SYS_WATCHDOG_REBOOT_WINDOW_HRS=2   # window in hours
576	SYS_WATCHDOG_REBOOT_LIMIT=3         # max reboots within the window
577	```
578
579	If the server reboots `SYS_WATCHDOG_REBOOT_LIMIT` times within the window, the watchdog
580	switches from rebooting to notifying only. Prevents a boot loop where the watchdog
581	reboots → something crashes again immediately → reboot again.
582
583	### Usage
584
585	```bash
586	stability_watchdog.sh              # single pass (called by watchdog_orchestrator.sh)
587	stability_watchdog.sh --dry-run    # run detection logic without rebooting
588	stability_watchdog.sh --status     # thresholds, current state, strike counts
589	stability_watchdog.sh --log        # verbose per-check output

safety concerns.

### Reboot Rate Limit

```bash
SYS_WATCHDOG_REBOOT_WINDOW_HRS=2   # window in hours
SYS_WATCHDOG_REBOOT_LIMIT=3         # max reboots within the window
```

If the server reboots `SYS_WATCHDOG_REBOOT_LIMIT` times within the window, the watchdog
switches from rebooting to notifying only. Prevents a boot loop where the watchdog
reboots → something crashes again immediately → reboot again.

### Usage

```bash
stability_watchdog.sh              # single pass (called by watchdog_orchestrator.sh)
stability_watchdog.sh --dry-run    # run detection logic without rebooting
stability_watchdog.sh --status     # thresholds, current state, strike counts
stability_watchdog.sh --log        # verbose per-check output


610	/root/.local/share/claude  → /mnt/user/appdata/claude-code/local/share/claude
611	```
612
613	Subsequent runs skip the migration and only create the symlinks.
614
615	### Calling from array_started.sh
616
617	To auto-restore Claude data on every boot without launching an interactive session:
618
619	```bash
620	# In /boot/config/go or array_started.sh:
621	/path/to/Tools/claude_startup.sh --setup
622	```
623
624	### Usage
625
626	```bash
627	claude_startup.sh           # set up persistent symlinks and launch Claude
628	claude_startup.sh --setup   # set up symlinks only — no launch (for array_started.sh)
629	```
630
631	---
632
633	## ramdisk_stop.sh
634
635	Safely stops the transcode ramdisk: redirects the transcode symlink to the SSD
636	fallback first (so Emby continues writing without interruption), then unmounts the
637	tmpfs and updates the state file. Primary use case is stopping the current ramdisk
638	before re-running `ramdisk_setup.sh` with new size or threshold values.
639

/root/.local/share/claude  → /mnt/user/appdata/claude-code/local/share/claude
```

Subsequent runs skip the migration and only create the symlinks.

### Calling from array_started.sh

To auto-restore Claude data on every boot without launching an interactive session:

```bash
# In /boot/config/go or array_started.sh:
/path/to/Tools/claude_startup.sh --setup
```

### Usage

```bash
claude_startup.sh           # set up persistent symlinks and launch Claude
claude_startup.sh --setup   # set up symlinks only — no launch (for array_started.sh)
```

---

## ramdisk_stop.sh

Safely stops the transcode ramdisk: redirects the transcode symlink to the SSD
fallback first (so Emby continues writing without interruption), then unmounts the
tmpfs and updates the state file. Primary use case is stopping the current ramdisk
before re-running `ramdisk_setup.sh` with new size or threshold values.


grep -n "play_state_sync\|--full\|FLAG\|RUNTIME" /boot/config/plugins/varaverk/Media/Manual-Media.md | grep -i "full\|play_state" | head -15

Check Manual-Media for play_state_sync --full coverage


grep -n "play_state_sync\|--full\|RUNTIME MODE\|FLAG" /boot/config/plugins/varaverk/Media/Manual-Media.md | head -20

Find play_state_sync section in Manual-Media


60	#     Show what would be synced without writing any state.
61	#
62	# play_state_sync.sh --status
63	#     Show configured servers, reachability, and user counts.
64	#
65	# play_state_sync.sh --full
66	#     Ignore PLAY_SYNC_DAYS — sync all played items (may be slow).
67	#
68	# play_state_sync.sh --log
69	#     Verbose output — show each item comparison.
70	#
71	# ==============================================================================================
72
73	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
74	source "$SCRIPT_DIR/../load_config.sh"
75
76	# ── Handle --full before parse_args ───────────────────────────────────────────
77	FULL_SYNC=false
78	_FILTERED=()
79	for _a in "$@"; do

#     Show what would be synced without writing any state.
#
# play_state_sync.sh --status
#     Show configured servers, reachability, and user counts.
#
# play_state_sync.sh --full
#     Ignore PLAY_SYNC_DAYS — sync all played items (may be slow).
#
# play_state_sync.sh --log
#     Verbose output — show each item comparison.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"

# ── Handle --full before parse_args ───────────────────────────────────────────
FULL_SYNC=false
_FILTERED=()
for _a in "$@"; do


563:## ━━━ FLAG REFERENCE ━━━


560
561	---
562
563	## ━━━ FLAG REFERENCE ━━━
564
565	### media_shares_permissions.sh
566
567	`media_shares_permissions.sh`
568	Apply correct ownership and permissions to all configured media shares. Safe to run
569	manually at any time — idempotent, only changes what's wrong.
570
571	`media_shares_permissions.sh --dry-run`
572	Show how many files and directories would be corrected per share. If unexpectedly large,
573	check container PUID/PGID settings first (PUID=99 PGID=100).
574
575	`media_shares_permissions.sh --status`
576	Show configured share list and ownership of the share roots.
577
578	`media_shares_permissions.sh --log`
579	Show ownership correction count per share and per file (verbose).
580
581	> On large libraries this runs 20-30 minutes. This is expected — millions of files with
582	> recursive walk takes time. Designed to run overnight in the maintenance window.
583
584	---
585
586	### media_cleaner.sh
587
588	`media_cleaner.sh anime`
589	Remove junk files from anime share folders using ANIME_FILE_PATTERNS.
590
591	`media_cleaner.sh media`
592	Remove junk files from media share folders using MEDIA_FILE_PATTERNS.
593
594	`media_cleaner.sh [profile] --dry-run`
595	Show what would be deleted without removing anything. Always run first when adding new
596	patterns or folders.
597
598	`media_cleaner.sh [profile] --status`
599	Show folder list and file patterns for the profile.
600
601	`media_cleaner.sh [profile] --log`
602	Show every file examined, not just those removed.
603
604	---
605
606	### lidarr_cleanup.sh / sonarr_cleanup.sh / radarr_cleanup.sh
607
608	`[script] --dry-run --log`
609	Preview every classification decision. **Always run this first.** See Safe Testing Procedure.
610
611	`[script]`
612	Live run — deletes confirmed orphans and junk, triggers Emby clean.
613
614	`[script] --log`
615	Live run with verbose per-file output.
616
617	`[script] --status`
618	Show configuration, API status, tracked file count, and last run stats.
619

---

## ━━━ FLAG REFERENCE ━━━

### media_shares_permissions.sh

`media_shares_permissions.sh`
Apply correct ownership and permissions to all configured media shares. Safe to run
manually at any time — idempotent, only changes what's wrong.

`media_shares_permissions.sh --dry-run`
Show how many files and directories would be corrected per share. If unexpectedly large,
check container PUID/PGID settings first (PUID=99 PGID=100).

`media_shares_permissions.sh --status`
Show configured share list and ownership of the share roots.

`media_shares_permissions.sh --log`
Show ownership correction count per share and per file (verbose).

> On large libraries this runs 20-30 minutes. This is expected — millions of files with
> recursive walk takes time. Designed to run overnight in the maintenance window.

---

### media_cleaner.sh

`media_cleaner.sh anime`
Remove junk files from anime share folders using ANIME_FILE_PATTERNS.

`media_cleaner.sh media`
Remove junk files from media share folders using MEDIA_FILE_PATTERNS.

`media_cleaner.sh [profile] --dry-run`
Show what would be deleted without removing anything. Always run first when adding new
patterns or folders.

`media_cleaner.sh [profile] --status`
Show folder list and file patterns for the profile.

`media_cleaner.sh [profile] --log`
Show every file examined, not just those removed.

---

### lidarr_cleanup.sh / sonarr_cleanup.sh / radarr_cleanup.sh

`[script] --dry-run --log`
Preview every classification decision. **Always run this first.** See Safe Testing Procedure.

`[script]`
Live run — deletes confirmed orphans and junk, triggers Emby clean.

`[script] --log`
Live run with verbose per-file output.

`[script] --status`
Show configuration, API status, tracked file count, and last run stats.


grep -n "play_state_sync" /boot/config/plugins/varaverk/Media/Manual-Media.md | head -15

Find all play_state_sync references in Manual-Media


620	`[script] --i-know-what-im-doing`
621	Bypass the MAX_DELETE_GB size threshold. Required when deletion exceeds the configured
622	limit. Long flag name is intentional — cannot be added accidentally.
623
624	`[script] --skip-strike-list`
625	Bypass the ORPHAN_AGE age check. Deletes RECENT files too — files that are under the
626	age threshold. Use when you know recent downloads are actually orphans.
627
628	`[script] --i-know-what-im-doing --skip-strike-list`
629	**NUCLEAR MODE** — age check and size threshold both bypassed. Deletes on first pass.
630	Use when you want a clean one-pass wipe of everything the arr doesn't track.
631	No recovery possible after deletion.
632
633	---
634
635	### arrs_failed_stalled_recovery.sh
636
637	`arrs_failed_stalled_recovery.sh`
638	Check all configured arrs for failed imports and stalled downloads. Blocklist + remove +
639	re-search for each problem item.
640
641	`arrs_failed_stalled_recovery.sh --dry-run`
642	Show what would be actioned per arr without making any changes.
643
644	`arrs_failed_stalled_recovery.sh --status`
645	Show configuration, arr reachability, and last recovery stats.
646
647	`arrs_failed_stalled_recovery.sh --log`
648	Verbose output per item per arr.
649
650	---
651
652	### arr_sync.sh
653
654	`arr_sync.sh`
655	Sync all arr types across all configured nodes.
656
657	`arr_sync.sh --dry-run`
658	Show what would be added/removed on each node without making changes.
659
660	`arr_sync.sh --status`
661	Show node configuration, arr reachability, and blocklist count.
662
663	`arr_sync.sh --log`
664	Verbose per-node, per-arr output.
665
666	`arr_sync.sh --blocklist-add [arr] [id] "[reason]"`
667	Remove item from all arrs and tombstone the ID. See Procedures above.
668
669	`arr_sync.sh --blocklist-remove [arr] [id]`

`[script] --i-know-what-im-doing`
Bypass the MAX_DELETE_GB size threshold. Required when deletion exceeds the configured
limit. Long flag name is intentional — cannot be added accidentally.

`[script] --skip-strike-list`
Bypass the ORPHAN_AGE age check. Deletes RECENT files too — files that are under the
age threshold. Use when you know recent downloads are actually orphans.

`[script] --i-know-what-im-doing --skip-strike-list`
**NUCLEAR MODE** — age check and size threshold both bypassed. Deletes on first pass.
Use when you want a clean one-pass wipe of everything the arr doesn't track.
No recovery possible after deletion.

---

### arrs_failed_stalled_recovery.sh

`arrs_failed_stalled_recovery.sh`
Check all configured arrs for failed imports and stalled downloads. Blocklist + remove +
re-search for each problem item.

`arrs_failed_stalled_recovery.sh --dry-run`
Show what would be actioned per arr without making any changes.

`arrs_failed_stalled_recovery.sh --status`
Show configuration, arr reachability, and last recovery stats.

`arrs_failed_stalled_recovery.sh --log`
Verbose output per item per arr.

---

### arr_sync.sh

`arr_sync.sh`
Sync all arr types across all configured nodes.

`arr_sync.sh --dry-run`
Show what would be added/removed on each node without making changes.

`arr_sync.sh --status`
Show node configuration, arr reachability, and blocklist count.

`arr_sync.sh --log`
Verbose per-node, per-arr output.

`arr_sync.sh --blocklist-add [arr] [id] "[reason]"`
Remove item from all arrs and tombstone the ID. See Procedures above.

`arr_sync.sh --blocklist-remove [arr] [id]`


grep -n "gate\|Gate\|play_state_sync" /boot/config/plugins/varaverk/Media/Manual-Media.md | head -20

Find gate references and play_state_sync in Manual-Media


375:  → Large count = stop, investigate root path before proceeding
510:  Investigate before proceeding — the arr may have a problem


30	# OPERATIONAL SAFEGUARDS
31	# ==============================================================================================
32	#
33	# Seven gates — ALL must pass before any file is touched:
34	#   1. Container running and not starting/unhealthy
35	#   2. API reachable
36	#   3. API version matches LIDARR_VERSION_MAJOR in master.conf
37	#   4. Artist count > 0
38	#   5. Tracked file count > 0
39	#   6. Tracked count >= LIDARR_MIN_TRACKED_PCT % of last known count
40	#   7. Deletion size < LIDARR_MAX_DELETE_GB — or --i-know-what-im-doing required
41	#
42	#   acquire_lock "wait"  — large scans take time, wait for previous run to finish
43	#   jq + curl validation — exits if either tool missing
44	#   DOCKER_TIMEOUT       — container checks protected against daemon hangs
45	#   Duplicate detection  — temp file of tracked paths, grep before delete
46	#   platform_require_cmd  — notify script validated before use
47	#   Silent by default    — orphans/junk warn(), clean library logs silently
48	#
49	# ==============================================================================================

# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Seven gates — ALL must pass before any file is touched:
#   1. Container running and not starting/unhealthy
#   2. API reachable
#   3. API version matches LIDARR_VERSION_MAJOR in master.conf
#   4. Artist count > 0
#   5. Tracked file count > 0
#   6. Tracked count >= LIDARR_MIN_TRACKED_PCT % of last known count
#   7. Deletion size < LIDARR_MAX_DELETE_GB — or --i-know-what-im-doing required
#
#   acquire_lock "wait"  — large scans take time, wait for previous run to finish
#   jq + curl validation — exits if either tool missing
#   DOCKER_TIMEOUT       — container checks protected against daemon hangs
#   Duplicate detection  — temp file of tracked paths, grep before delete
#   platform_require_cmd  — notify script validated before use
#   Silent by default    — orphans/junk warn(), clean library logs silently
#
# ==============================================================================================


grep -n "Six gate\|six gate\|lidarr.*gate\|gate.*lidarr" /boot/config/plugins/varaverk/Media/Manual-Media.md

Find the six gates claim for lidarr in Manual-Media


30	environment in its Docker template. Common culprits: SABnzbd, qBittorrent, slskd.
31	Once fixed, this script corrects 0 files per run — it becomes a pure daily failsafe.
32
33	---
34
35	## ━━━ ARR CLEANUP — FILE CLASSIFICATION ━━━
36
37	Every file found on disk during an arr cleanup run falls into exactly one category:
38
39	```
40	TRACKED    → arr API returned this exact path                   → leave it alone
41	PROTECTED  → matches ARR_PROTECTED_PATTERNS                     → never delete
42	ORPHAN     → media extension, not tracked, old enough           → delete
43	JUNK       → not a media extension, not protected               → delete (any age)
44	RECENT     → not tracked, under ARR_ORPHAN_AGE days             → skip (may be mid-import)
45	```
46
47	**Why protected patterns are critical:** arrs generate artwork (`*.jpg`), metadata
48	(`*.nfo`), and subtitles/lyrics that do NOT appear in the tracked file API response.
49	Without protection, these would be classified as orphans and deleted — removing cover art

environment in its Docker template. Common culprits: SABnzbd, qBittorrent, slskd.
Once fixed, this script corrects 0 files per run — it becomes a pure daily failsafe.

---

## ━━━ ARR CLEANUP — FILE CLASSIFICATION ━━━

Every file found on disk during an arr cleanup run falls into exactly one category:

```
TRACKED    → arr API returned this exact path                   → leave it alone
PROTECTED  → matches ARR_PROTECTED_PATTERNS                     → never delete
ORPHAN     → media extension, not tracked, old enough           → delete
JUNK       → not a media extension, not protected               → delete (any age)
RECENT     → not tracked, under ARR_ORPHAN_AGE days             → skip (may be mid-import)
```

**Why protected patterns are critical:** arrs generate artwork (`*.jpg`), metadata
(`*.nfo`), and subtitles/lyrics that do NOT appear in the tracked file API response.
Without protection, these would be classified as orphans and deleted — removing cover art


grep -n "gate\|Gate" /boot/config/plugins/varaverk/Media/Manual-Media.md | head -10

Find any gate references in Manual-Media


grep -n "gate\|Gate" /boot/config/plugins/varaverk/Media/sonarr_cleanup.sh | head -5
grep -n "gate\|Gate" /boot/config/plugins/varaverk/Media/radarr_cleanup.sh | head -5

Check gate count in sonarr and radarr cleanup headers


37:# Six gates — ALL must pass before any file is touched:
37:# Six gates — ALL must pass before any file is touched:


Good — the "six gates for lidarr" in the Manual is a false positive (the Manual doesn't state that count; the agent misread). Now I have a clean list. Let me make all the fixes:


140	- TMDB ID match preferred over title; title fallback handles edge cases
141
142	---
143
144	## fallback_state_reset.sh
145
146	> **Not yet built.** Use the manual workaround below.
147
148	Planned: reset the fallback state file to NORMAL and clear all tier flags. State file
149	only — does NOT start or stop any containers.
150
151	### When to Use
152
153	```
154	After fallback_test.sh didn't complete cleanly
155	  → state left in FALLBACK but containers are actually back to normal
156
157	After a failed handback
158	  → state shows FALLBACK but remote is back up and containers are split
159
160	After killing fallback.sh directly (not gracefully via SIGTERM)
161	  → state is unknown, cycle was interrupted mid-operation
162
163	After a dev/debug session
164	  → state left in a non-NORMAL state from testing
165	```
166
167	### Manual Workaround
168
169	```bash
170	# Verify before resetting:
171
172	# Right containers on right server?
173	continuous_scripts_status.sh        # shows fallback current state
174
175	# DDNS pointing correctly?
176	nslookup Gmer4Lfe.com               # confirm it resolves to the right IP
177
178	# fallback.sh not running?
179	pgrep -f "fallback.sh"              # empty output = not running

- TMDB ID match preferred over title; title fallback handles edge cases

---

## fallback_state_reset.sh

> **Not yet built.** Use the manual workaround below.

Planned: reset the fallback state file to NORMAL and clear all tier flags. State file
only — does NOT start or stop any containers.

### When to Use

```
After fallback_test.sh didn't complete cleanly
  → state left in FALLBACK but containers are actually back to normal

After a failed handback
  → state shows FALLBACK but remote is back up and containers are split

After killing fallback.sh directly (not gracefully via SIGTERM)
  → state is unknown, cycle was interrupted mid-operation

After a dev/debug session
  → state left in a non-NORMAL state from testing
```

### Manual Workaround

```bash
# Verify before resetting:

# Right containers on right server?
continuous_scripts_status.sh        # shows fallback current state

# DDNS pointing correctly?
nslookup Gmer4Lfe.com               # confirm it resolves to the right IP

# fallback.sh not running?
pgrep -f "fallback.sh"              # empty output = not running


115	## ━━━ SCRIPTS IN THIS FOLDER ━━━
116
117	| Script | What It Fixes | When to Run |
118	|--------|--------------|-------------|
119	| `fallback_state_reset.sh` | State file stuck in FALLBACK after test or failed handback | After fallback testing or manual intervention |
120	| `watchdog_skip_list_manager.sh` | Container stuck on watchdog skip list after fixing root cause | After fixing a container that hit the restart loop limit |
121	| `bulk_permissions_repair.sh` | Files owned by wrong user after admin copy or bad container config | When arr operations fail due to permissions |
122	| `container_data_export.sh` | Need a clean backup before a risky container update or migration | Before major updates, appdata migrations, or container removals |
123	| `emby_database_repair.sh` | Emby crashing with database errors after power loss or crash | When Emby logs show corruption or repeated crashes |
124	| `zfs_pool_scrub.sh` | Verify ZFS pool integrity — catch silent corruption before it spreads | Monthly, or after any disk or power event |
125	| `recreate_shares.sh` | Share directories missing after fresh install or disk rebuild | After fresh unRAID install or disk replacement on HOST2 |
126	| `continuous_scripts_status.sh` | Need a live view of watchdog and fallback state | Any time — manual dashboard, no schedule |
127	| `claude_startup.sh` | Claude Code session setup after reboot — symlinks persistent storage | After each unRAID reboot, or called by array_started.sh |
128	| `ramdisk_stop.sh` | Safely stop the transcode ramdisk — redirect symlink to SSD, unmount, update state | Before re-running ramdisk_setup.sh with new size or thresholds |
129	| `emby_to_lidarr_sync.sh` | Add all Emby album artists not yet tracked in Lidarr | After Lidarr setup, database wipe, or when you suspect gaps |
130	| `emby_to_sonarr_sync.sh` | Add all Emby TV series not yet tracked in Sonarr | After Sonarr setup, database wipe, or when you suspect gaps |
131	| `emby_to_radarr_sync.sh` | Add all Emby movies not yet tracked in Radarr | After Radarr setup, database wipe, or when you suspect gaps |
132
133	---
134
135	## ━━━ HOW THE SCRIPTS RELATE ━━━
136
137	All Tools scripts are independent — none call each other, none are called by other Tools.
138
139	```
140	Situation arises
141	      │
142	      ▼
143	┌──────────────────────────────────────────────────────────────────────┐
144	│  Tools/                          Run directly when needed             │

## ━━━ SCRIPTS IN THIS FOLDER ━━━

| Script | What It Fixes | When to Run |
|--------|--------------|-------------|
| `fallback_state_reset.sh` | State file stuck in FALLBACK after test or failed handback | After fallback testing or manual intervention |
| `watchdog_skip_list_manager.sh` | Container stuck on watchdog skip list after fixing root cause | After fixing a container that hit the restart loop limit |
| `bulk_permissions_repair.sh` | Files owned by wrong user after admin copy or bad container config | When arr operations fail due to permissions |
| `container_data_export.sh` | Need a clean backup before a risky container update or migration | Before major updates, appdata migrations, or container removals |
| `emby_database_repair.sh` | Emby crashing with database errors after power loss or crash | When Emby logs show corruption or repeated crashes |
| `zfs_pool_scrub.sh` | Verify ZFS pool integrity — catch silent corruption before it spreads | Monthly, or after any disk or power event |
| `recreate_shares.sh` | Share directories missing after fresh install or disk rebuild | After fresh unRAID install or disk replacement on HOST2 |
| `continuous_scripts_status.sh` | Need a live view of watchdog and fallback state | Any time — manual dashboard, no schedule |
| `claude_startup.sh` | Claude Code session setup after reboot — symlinks persistent storage | After each unRAID reboot, or called by array_started.sh |
| `ramdisk_stop.sh` | Safely stop the transcode ramdisk — redirect symlink to SSD, unmount, update state | Before re-running ramdisk_setup.sh with new size or thresholds |
| `emby_to_lidarr_sync.sh` | Add all Emby album artists not yet tracked in Lidarr | After Lidarr setup, database wipe, or when you suspect gaps |
| `emby_to_sonarr_sync.sh` | Add all Emby TV series not yet tracked in Sonarr | After Sonarr setup, database wipe, or when you suspect gaps |
| `emby_to_radarr_sync.sh` | Add all Emby movies not yet tracked in Radarr | After Radarr setup, database wipe, or when you suspect gaps |

---

## ━━━ HOW THE SCRIPTS RELATE ━━━

All Tools scripts are independent — none call each other, none are called by other Tools.

```
Situation arises
      │
      ▼
┌──────────────────────────────────────────────────────────────────────┐
│  Tools/                          Run directly when needed             │


180
181	# Both servers Tailscale connected?
182	tailscale status                    # both hosts should show active
183
184	# Check current state file:
185	cat /boot/config/fallback_state.db
186
187	# Reset to NORMAL (only after confirming containers and DDNS are correct):
188	echo "state=NORMAL" > /boot/config/fallback_state.db
189	```
190
191	Resetting during an actual fallback causes fallback.sh to think everything is normal
192	and stop covering the remote — services go offline until the next detection cycle.
193
194	### What the State File Contains
195
196	```bash
197	state=NORMAL
198	fallback_start=0
199	handback_strikes=0
200	tier2_started=false
201	tier3_started=false
202	tier4_started=false
203	```
204
205	---
206
207	## watchdog_skip_list_manager.sh
208
209	View and manage the persistent container skip list used by `docker_watchdog.sh`.

# Both servers Tailscale connected?
tailscale status                    # both hosts should show active

# Check current state file:
cat /boot/config/fallback_state.db

# Reset to NORMAL (only after confirming containers and DDNS are correct):
echo "state=NORMAL" > /boot/config/fallback_state.db
```

Resetting during an actual fallback causes fallback.sh to think everything is normal
and stop covering the remote — services go offline until the next detection cycle.

### What the State File Contains

```bash
state=NORMAL
fallback_start=0
handback_strikes=0
tier2_started=false
tier3_started=false
tier4_started=false
```

---

## watchdog_skip_list_manager.sh

View and manage the persistent container skip list used by `docker_watchdog.sh`.


70	so behaviour stays consistent without a shell dependency.
71
72	---
73
74	## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
75
76	| Script | Role | When It Runs |
77	|--------|------|--------------|
78	| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |
79
80	---
81
82
83	## ━━━ THE PLATFORM ADAPTER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
84
85	`Plugin/unraid/adapter.sh` is the Unraid platform adapter. It is sourced automatically
86	by `load_config.sh` whenever `PLATFORM=unraid` is detected (via `/etc/unraid-version`).
87
88	Every bash script in the ecosystem calls `platform_*()` functions instead of OS-specific
89	commands directly. The adapter translates those calls into Unraid-specific implementations.
90
91	```
92	platform_storage_healthy          # is the array up and shfs mounted?
93	platform_is_maintenance_running   # parity check or sync in progress?
94	platform_is_service_running       # is a named service process alive?
95	platform_restart_service          # restart via rc.d (Unraid) or systemctl (future)
96	platform_stop_service             # stop a named service
97	platform_is_mover_running         # Unraid mover active?
98	platform_get_mover_pid            # PID of the mover process
99	platform_stop_user_scripts        # kill Unraid user.scripts background jobs

so behaviour stays consistent without a shell dependency.

---

## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| Script | Role | When It Runs |
|--------|------|--------------|
| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |

---

## ━━━ THE PLATFORM ADAPTER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

`Plugin/unraid/adapter.sh` is the Unraid platform adapter. It is sourced automatically
by `load_config.sh` whenever `PLATFORM=unraid` is detected (via `/etc/unraid-version`).

Every bash script in the ecosystem calls `platform_*()` functions instead of OS-specific
commands directly. The adapter translates those calls into Unraid-specific implementations.

```
platform_storage_healthy          # is the array up and shfs mounted?
platform_is_maintenance_running   # parity check or sync in progress?
platform_is_service_running       # is a named service process alive?
platform_restart_service          # restart via rc.d (Unraid) or systemctl (future)
platform_stop_service             # stop a named service
platform_is_mover_running         # Unraid mover active?
platform_get_mover_pid            # PID of the mover process
platform_stop_user_scripts        # kill Unraid user.scripts background jobs


Now I'll make all the fixes in parallel batches.

**Batch 1 — Script header and Transcodes README:**


Edit

#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 2)
#   SYS_WATCHDOG_MAX_REBOOTS        — max reboots in window before giving up (default: 3)
#   SYS_WATCHDOG_STRIKES            — consecutive failures before reboot (default: 3)

#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 12)
#   SYS_WATCHDOG_MAX_REBOOTS        — max reboots in window before giving up (default: 3)
#   SYS_WATCHDOG_STRIKE_LIMIT       — consecutive failures before reboot (default: 2)


The file /boot/config/plugins/varaverk/Watchdogs/stability_watchdog.sh has been updated successfully. (file state is current in your context — no need to Read it back)

#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 2)
#   SYS_WATCHDOG_MAX_REBOOTS        — max reboots in window before giving up (default: 3)
#   SYS_WATCHDOG_STRIKES            — consecutive failures before reboot (default: 3)

#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 12)
#   SYS_WATCHDOG_MAX_REBOOTS        — max reboots in window before giving up (default: 3)
#   SYS_WATCHDOG_STRIKE_LIMIT       — consecutive failures before reboot (default: 2)

#   Full variable listing in master.conf. Key variables:

-#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 2)

+#   SYS_WATCHDOG_REBOOT_WINDOW_HRS  — reboot rate limit window (default: 12)

-#   SYS_WATCHDOG_STRIKES            — consecutive failures before reboot (default: 3)

+#   SYS_WATCHDOG_STRIKE_LIMIT       — consecutive failures before reboot (default: 2)

#   SYS_WATCHDOG_OOM_LIMIT          — OOM kills/cycle to trigger URGENT bypass (default: 3)

#   SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED — containers exempt from memory shutdown


SYS_WATCHDOG_REBOOT_WINDOW_HRS=2   # window in hours

SYS_WATCHDOG_REBOOT_WINDOW_HRS=12  # window in hours


The file /boot/config/plugins/varaverk/Watchdogs/Manual-Watchdogs.md has been updated successfully. (file state is current in your context — no need to Read it back)

-SYS_WATCHDOG_REBOOT_WINDOW_HRS=2   # window in hours

+SYS_WATCHDOG_REBOOT_WINDOW_HRS=12  # window in hours

SYS_WATCHDOG_REBOOT_LIMIT=3         # max reboots within the window


| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 3 min via transcode_management.sh — runs first |
| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 3 min via transcode_management.sh — runs second |

| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 7 minutes via transcode_management.sh — runs first |
| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 7 minutes via transcode_management.sh — runs second |


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


130	## ━━━ SCRIPTS IN THIS FOLDER ━━━
131
132	| Script | Role | When It Runs |
133	|--------|------|-------------|
134	| `ramdisk_setup.sh` | Create tmpfs, SSD fallback dir, symlink, transcoding-temp | At array start (via array_started.sh) |
135	| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 3 min via transcode_management.sh — runs first |
136	| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 3 min via transcode_management.sh — runs second |
137
138	---
139
140	## ━━━ HOW THE SCRIPTS RELATE ━━━
141
142	```
143	Array starts
144	    │

## ━━━ SCRIPTS IN THIS FOLDER ━━━

| Script | Role | When It Runs |
|--------|------|-------------|
| `ramdisk_setup.sh` | Create tmpfs, SSD fallback dir, symlink, transcoding-temp | At array start (via array_started.sh) |
| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 3 min via transcode_management.sh — runs first |
| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 3 min via transcode_management.sh — runs second |

---

## ━━━ HOW THE SCRIPTS RELATE ━━━

```
Array starts
    │


1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= fallback State Reset ===========================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Resets the fallback state file to NORMAL and clears all tier flags. Use when
9	# the state file is stuck after fallback testing, a failed handback, manual
10	# intervention, or fallback.sh being killed mid-cycle.
11	#
12	# Writes a fresh state file with:
13	#   state=NORMAL / fallback_start=0 / handback_strikes=0
14	#   tier2_started=false / tier3_started=false / tier4_started=false
15	#
16	# Does NOT start or stop containers — state file only. After reset, fallback.sh
17	# resumes from NORMAL on its next cycle.
18	#
19	# WARNING: Only run when you have verified the stack is actually in a normal
20	# state — right containers on the right server, DDNS correct, no active fallback
21	# in progress. Resetting state during a real fallback causes fallback.sh to stop
22	# covering the remote server until the next detection cycle.
23	#
24	# ==============================================================================================
25	# OPERATIONAL SAFEGUARDS
26	# ==============================================================================================
27	#
28	# Active Fallback Detection
29	#   Checks whether fallback.sh is currently running and warns if so. A reset
30	#   during an active cycle causes fallback.sh to lose its state on the next read.
31	#
32	# Single Instance Lock
33	#   acquire_lock prevents concurrent resets.
34	#
35	# flock on State Write
36	#   The state file write is protected with flock — prevents a race condition
37	#   with fallback.sh reading the file mid-cycle.
38	#
39	# Confirmation Required
40	#   Interactive mode prompts for YES before writing. Use --force to bypass in
41	#   non-interactive contexts (cron, scripts).
42	#
43	# Notification Validated
44	#   platform_require_cmd confirms the notify script is present before use.
45	#
46	# ==============================================================================================
47	# RUNTIME MODES
48	# ==============================================================================================
49	#
50	# fallback_state_reset.sh
51	#     Show current state and prompt for YES before resetting.
52	#
53	# fallback_state_reset.sh --dry-run
54	#     Show current state and what the new state file would contain. No write.
55	#
56	# fallback_state_reset.sh --status
57	#     Show current state file contents and exit.
58	#
59	# fallback_state_reset.sh --force
60	#     Reset without interactive confirmation. Safe for scripted use.
61	#
62	# fallback_state_reset.sh --force --dry-run
63	#     Dry run without the confirmation prompt.
64	#
65	# ==============================================================================================
66
67	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
68
69	source "$SCRIPT_DIR/../load_config.sh"
70
71	# ── Handle --force flag before parse_args ─────────────────────────────────────────────────────
72	FORCE=false
73	FILTERED_ARGS=()
74	for arg in "$@"; do
75	    case "$arg" in
76	        --force) FORCE=true ;;
77	        *)       FILTERED_ARGS+=("$arg") ;;
78	    esac
79	done
80

#!/bin/bash
# ==============================================================================================
# ============================= fallback State Reset ===========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Resets the fallback state file to NORMAL and clears all tier flags. Use when
# the state file is stuck after fallback testing, a failed handback, manual
# intervention, or fallback.sh being killed mid-cycle.
#
# Writes a fresh state file with:
#   state=NORMAL / fallback_start=0 / handback_strikes=0
#   tier2_started=false / tier3_started=false / tier4_started=false
#
# Does NOT start or stop containers — state file only. After reset, fallback.sh
# resumes from NORMAL on its next cycle.
#
# WARNING: Only run when you have verified the stack is actually in a normal
# state — right containers on the right server, DDNS correct, no active fallback
# in progress. Resetting state during a real fallback causes fallback.sh to stop
# covering the remote server until the next detection cycle.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Active Fallback Detection
#   Checks whether fallback.sh is currently running and warns if so. A reset
#   during an active cycle causes fallback.sh to lose its state on the next read.
#
# Single Instance Lock
#   acquire_lock prevents concurrent resets.
#
# flock on State Write
#   The state file write is protected with flock — prevents a race condition
#   with fallback.sh reading the file mid-cycle.
#
# Confirmation Required
#   Interactive mode prompts for YES before writing. Use --force to bypass in
#   non-interactive contexts (cron, scripts).
#
# Notification Validated
#   platform_require_cmd confirms the notify script is present before use.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# fallback_state_reset.sh
#     Show current state and prompt for YES before resetting.
#
# fallback_state_reset.sh --dry-run
#     Show current state and what the new state file would contain. No write.
#
# fallback_state_reset.sh --status
#     Show current state file contents and exit.
#
# fallback_state_reset.sh --force
#     Reset without interactive confirmation. Safe for scripted use.
#
# fallback_state_reset.sh --force --dry-run
#     Dry run without the confirmation prompt.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "$SCRIPT_DIR/../load_config.sh"

# ── Handle --force flag before parse_args ─────────────────────────────────────────────────────
FORCE=false
FILTERED_ARGS=()
for arg in "$@"; do
    case "$arg" in
        --force) FORCE=true ;;
        *)       FILTERED_ARGS+=("$arg") ;;
    esac
done


1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= Docker Prune Images ============================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Removes orphaned Docker images. Two modes:
9	#
10	# Default — dangling only (safe, fast):
11	#   Removes untagged images (no name, no container reference). These accumulate
12	#   after container updates pull a new image, leaving the old one untagged.
13	#   Running containers are never affected.
14	#
15	# --all — full orphan cleanup:
16	#   Step 1: removes stopped containers (exited/created state).
17	#   Step 2: removes all images not used by any running container.
18	#   Use this to clear tagged images left behind by removed or stopped apps.
19	#   CAUTION: also removes intentionally stopped containers — only run when you
20	#   know all stopped containers are safe to delete.
21	#
22	# ==============================================================================================
23	# RUNTIME MODES
24	# ==============================================================================================
25	#
26	# docker_prune_images.sh
27	#     Remove dangling (untagged) images only.
28	#
29	# docker_prune_images.sh --all
30	#     Remove stopped containers, then remove all unused images.
31	#
32	# docker_prune_images.sh --dry-run
33	#     Show what would be removed without making changes.
34	#
35	# docker_prune_images.sh --status
36	#     Show dangling images and stopped containers. No changes.
37	#
38	# ==============================================================================================
39
40	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
41
42	source "$SCRIPT_DIR/../load_config.sh"
43
44	# ── Handle --all before parse_args ────────────────────────────────────────────
45	ALL_MODE=false
46	FILTERED_ARGS=()
47	for _arg in "$@"; do
48	    [[ "$_arg" == "--all" ]] && ALL_MODE=true || FILTERED_ARGS+=("$_arg")
49	done
50	parse_args "${FILTERED_ARGS[@]}"
51
52	# ==============================================================================================
53	# ━━━ Setup ━━━
54	# ==============================================================================================
55	if [[ "$EUID" -ne 0 ]]; then
56	    error "Must be run as root"
57	    exit 1
58	fi
59
60	acquire_lock
61
62	# ==============================================================================================
63	# ━━━ Status ━━━
64	# ==============================================================================================
65	if [[ "$SHOW_STATUS" == true ]]; then
66	    echo ""
67	    echo "━━━━━ $ICON_SUMMARY DOCKER PRUNE IMAGES STATUS ━━━━━"
68
69	    DANGLING=$(docker images -f "dangling=true" --format "{{.ID}}\t{{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}" 2>/dev/null)
70	    STOPPED=$(docker ps -a --filter "status=exited" --filter "status=created" \

#!/bin/bash
# ==============================================================================================
# ============================= Docker Prune Images ============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Removes orphaned Docker images. Two modes:
#
# Default — dangling only (safe, fast):
#   Removes untagged images (no name, no container reference). These accumulate
#   after container updates pull a new image, leaving the old one untagged.
#   Running containers are never affected.
#
# --all — full orphan cleanup:
#   Step 1: removes stopped containers (exited/created state).
#   Step 2: removes all images not used by any running container.
#   Use this to clear tagged images left behind by removed or stopped apps.
#   CAUTION: also removes intentionally stopped containers — only run when you
#   know all stopped containers are safe to delete.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# docker_prune_images.sh
#     Remove dangling (untagged) images only.
#
# docker_prune_images.sh --all
#     Remove stopped containers, then remove all unused images.
#
# docker_prune_images.sh --dry-run
#     Show what would be removed without making changes.
#
# docker_prune_images.sh --status
#     Show dangling images and stopped containers. No changes.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "$SCRIPT_DIR/../load_config.sh"

# ── Handle --all before parse_args ────────────────────────────────────────────
ALL_MODE=false
FILTERED_ARGS=()
for _arg in "$@"; do
    [[ "$_arg" == "--all" ]] && ALL_MODE=true || FILTERED_ARGS+=("$_arg")
done
parse_args "${FILTERED_ARGS[@]}"

# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root"
    exit 1
fi

acquire_lock

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY DOCKER PRUNE IMAGES STATUS ━━━━━"

    DANGLING=$(docker images -f "dangling=true" --format "{{.ID}}\t{{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}" 2>/dev/null)
    STOPPED=$(docker ps -a --filter "status=exited" --filter "status=created" \


Now making the fixes. Starting with Transcodes README and Manual-Tools:


| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 3 min via transcode_management.sh — runs first |
| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 3 min via transcode_management.sh — runs second |

| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 7 minutes via transcode_management.sh — runs first |
| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 7 minutes via transcode_management.sh — runs second |


The file /boot/config/plugins/varaverk/Transcodes/README-Transcoding.md has been updated successfully. (file state is current in your context — no need to Read it back)

| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 7 minutes via transcode_management.sh — runs first |
| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 7 minutes via transcode_management.sh — runs second |

# ━━━━━ TRANSCODES ━━━━━

**Ramdisk-based transcode storage with automatic SSD fallback.** Emby transcodes to RAM
at full speed. When the ramdisk fills, new sessions shift to SSD automatically —
without interrupting anything already playing. When pressure drops, new sessions shift
back to RAM.

> **Three configuration requirements that are not obvious and were all discovered the
> hard way in production.** The `transcoding-temp` directory must be pre-created on
> the ramdisk or Emby finds the SSD version and routes all sessions there until
> restarted. GPU containers require `--gpus "device=UUID"` in Extra Parameters —
> not `--runtime=nvidia`. Do **not** use `bind-propagation=shared` on Unraid 7.3+
> (Docker 29.x / runc v1.3.5+) — it crashes container start; the ramdisk is already
> MS\_SHARED at the kernel level. Additionally: any GPU-accelerated sidecar (OCR plugins,
> credit detection) that holds VRAM and never releases it will starve Emby and Jellyfin
> of VRAM for transcoding — Jellyfin hard-fails, Emby silently falls back to CPU.
> All three are documented in Manual-Transcoding.md.

---

## ━━━ REQUIRED EXTRA PARAMETERS ━━━

> **Stop. Set this before starting Emby or Jellyfin. If you Google how to add GPU
> access to a Docker container on unRAID you will find the wrong answer.** Every
> forum post and guide shows `--runtime=nvidia` + `NVIDIA_VISIBLE_DEVICES`. That
> method breaks on container rebuilds. Use `--gpus` instead.

In the unRAID Docker template, open **Advanced View** and paste the following into
the **Extra Parameters** field. Do not use the path mapping UI for the transcode
directory — it does not support the `--mount` syntax.

**GPU-accelerated (Emby, Jellyfin with NVENC/NVDEC) — use this:**
```
--gpus "device=GPU-62e1659d-1ed4-935f-3df3-4bb4339438f1" --pids-limit=0 --mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode
```

**Non-GPU — use this:**
```
--mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode
```

In Emby and Jellyfin's transcoding settings, set the transcode temp path to `/ext-ram-transcode`.

Find your GPU UUID: `nvidia-smi -L`
HOST1 GPU UUID (Quadro P2000): `GPU-62e1659d-1ed4-935f-3df3-4bb4339438f1`

**→ Full explanation: [Manual-Transcoding.md](Manual-Transcoding.md)**

---

## ━━━ THE PROBLEM THAT BUILT THIS ━━━

**Three Storage Options, None Perfect on Their Own**
Hard drives: seek times cause buffering on multi-stream transcoding. SSD: fast enough,
but constant small file writes at Emby volume accelerate wear over months. RAM: fastest,
no wear, files vanish instantly on session end — but limited by available memory.
Fix: RAM by default, SSD as a safety net. The system manages the transition automatically.

**Changing Transcode Location Requires Restarting Emby**
Configuring Emby to switch between ramdisk and SSD requires a restart. Restarting
during active streams drops everyone. A 7-person household with 5 Live TV streams at
9pm is not a good moment to restart Emby.
Fix: symlink indirection. Emby points at a fixed path. The symlink target changes.
ffmpeg resolves the symlink once at session start — existing sessions are completely
unaffected by flips. Only new sessions follow the new target.

**Docker Bind Mount Silently Ignored After First Flip** *(historical — resolved differently)*
Symlink flip from ramdisk → SSD worked. Flip back: nothing. All new sessions still land
on SSD. The symlink on the host is correct. Emby doesn't see it.
Original fix was `bind-propagation=shared` — but this crashes runc v1.3.5+ (Docker 29.x,
Unraid 7.3+) on any container start. The ramdisk tmpfs is `MS_SHARED` at the kernel level,
so propagation is inherited automatically without specifying it in Docker. Do not add
`bind-propagation=shared` to Extra Parameters.

**Sessions Drifting to SSD After a Day of Operation**
System working correctly for hours, then sessions gradually drift to SSD despite the
ramdisk having plenty of space.
Cause: cleanup was removing the empty `transcoding-temp` directory from the ramdisk.
Emby then found the SSD fallback version and routed all sessions there.
Fix: `transcoding-temp` is excluded from cleanup by name. `ramdisk_setup.sh` pre-creates
it at mount time. Both protections together prevent this permanently.

**lsof Per File on a Live TV System**
Early cleanup called `lsof filename` per file to check if anything had it open. On a busy
Live TV night with 5 simultaneous streams, the ramdisk contains thousands of HLS segment
files — thousands of subprocess calls every 7 minutes.
Fix: lsof called once per location to build a complete open-file map. All subsequent
checks are O(1) lookups against that map.

---

## ━━━ WHAT THIS FOLDER DOES ━━━

Three scripts, one goal: keep transcodes on RAM, fall back to SSD when needed.

`ramdisk_setup.sh` runs at array start — creates the tmpfs, SSD fallback directory,
symlink, and pre-creates `transcoding-temp`. Everything that must exist before Emby starts.

`transcode_cleanup.sh` runs first in every 7-minute cycle — removes stale files from both
ramdisk and SSD. Cleans up before usage is measured, so the manager sees real load.

`transcode_manager.sh` runs second — measures ramdisk usage, flips the symlink if
thresholds are crossed, runs safety checks, displays active sessions, writes the daily log.

The symlink is the mechanism that makes this seamless. Emby writes to a fixed path. That
path is a symlink whose target is managed at runtime. Sessions in progress never notice.

---

## ━━━ RELATIONSHIP TO OTHER FOLDERS ━━━

```
System_Essentials/
  array_started.sh ──────────────────────────────► ramdisk_setup.sh (at array start)

Orchestrators/
  transcode_management.sh ──── cleanup first ──► transcode_cleanup.sh
                           ──── then manager ──► transcode_manager.sh
                           (every 7 minutes — order non-negotiable)

Monitors/
  weekly_health_digest.sh ◄─── reads ──────────── TRANSCODE_DAILY_LOG
```

Do not schedule `transcode_cleanup.sh` or `transcode_manager.sh` directly.
Both are called by `transcode_management.sh` in the correct order.

---

## ━━━ SCRIPTS IN THIS FOLDER ━━━

| Script | Role | When It Runs |
|--------|------|-------------|
| `ramdisk_setup.sh` | Create tmpfs, SSD fallback dir, symlink, transcoding-temp | At array start (via array_started.sh) |
| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 3 min via transcode_management.sh — runs first |
| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 3 min via transcode_management.sh — runs second |

---

## ━━━ HOW THE SCRIPTS RELATE ━━━

```
Array starts
    │
    ▼
ramdisk_setup.sh
    Creates: /mnt/ramdisk_transcodes (tmpfs)
             /mnt/ramdisk_transcodes/transcoding-temp/
             /mnt/cache/Temp_Storage/Emby/Transcodes/ (SSD fallback)
             /mnt/ram-transcode → /mnt/ramdisk_transcodes (symlink)
    │
    ▼
Emby starts, reads transcode path from config
    Sees: /ext-ram-transcode (bind-mounted from /mnt/ram-transcode)
    All new sessions write to: /mnt/ram-transcode → /mnt/ramdisk_transcodes/

Every 7 minutes (transcode_management.sh):
    │
    ├─ transcode_cleanup.sh
    │    Remove files older than TRANSCODE_MAX_AGE, not open by any process
    │    transcoding-temp: never deleted
    │    If ramdisk recovered below RAMDISK_LOW_GB → trigger flip-back
    │
    └─ transcode_manager.sh
         Safety checks (symlink, ramdisk mount, transcoding-temp, permissions)
         smart mode: ramdisk > RAMDISK_WARN_GB → flip symlink to SSD
                     ramdisk < RAMDISK_LOW_GB  → flip symlink back to ramdisk
         Session display (all TRANSCODE_SERVERS)
         Append to TRANSCODE_DAILY_LOG
```

-| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 3 min via transcode_management.sh — runs first |

-| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 3 min via transcode_management.sh — runs second |

+| `transcode_cleanup.sh` | Remove stale files, check for flip-back opportunity | Every 7 minutes via transcode_management.sh — runs first |

+| `transcode_manager.sh` | Check usage, flip symlink, safety checks, session display, daily log | Every 7 minutes via transcode_management.sh — runs second |