if [ ! -e "$plugindir" ] && [ ! -L "$plugindir" ]; then
Root cause (DOGFOOD-008): ~/.hermes/plugins/coding-hermes is a symlink into the /fleet checkout. After a checkout move, its target path was typoed (missing -hermes-). Because nothing verified the link, the breakage went silent — the plugin just quietly pointed nowhere. The naive fix ("check it's a dir") is insufficient: a typo like .../coding can point at an existing but wrong directory, which passes any plain -d check. So the verifier checks three things: the path exists, it resolves to a real directory, and the fully-resolved target's basename is exactly coding-hermes.
deploy/scheduler-verify.sh (~/deploy/scheduler-verify.sh) — cron-friendly: silent exit 0 on success (no cron mail noise), loud structured SCHEDULER-VERIFY FAIL [timestamp] reason on stderr with exit 1 on any failure.
#!/usr/bin/env bash
set -u
plugin_name="${PLUGIN_NAME:-coding-hermes}"
verbose=0
case "${1:-}" in
-v|--verbose) verbose=1 ;;
"") ;;
*) printf 'SCHEDULER-VERIFY: unknown argument: %s\n' "$1" >&2; exit 2 ;;
esac
fail() {
printf 'SCHEDULER-VERIFY FAIL [%s] %s\n' "$(date -Is 2>/dev/null || date)" "$1" >&2
exit 1
}
if [ -z "${HOME:-}" ]; then
fail "HOME is not set; cannot locate ${plugin_name} plugin"
fi
plugin_dir="${HOME}/.hermes/plugins/${plugin_name}"
# 1) Path must exist at all (file, dir, or symlink).
if [ ! -e "$plugin_dir" ] && [ ! -L "$plugin_dir" ]; then
fail "plugin path missing: ${plugin_dir}"
fi
# 2) A regular file at the plugin path is never valid.
if [ -f "$plugin_dir" ] && [ ! -L "$plugin_dir" ]; then
fail "plugin path is a regular file, not a directory or symlink: ${plugin_dir}"
fi
# 3) Fully resolve. readlink -f fails (empty, non-zero) on dangling links.
resolved="$(readlink -f "$plugin_dir" 2>/dev/null)" || {
fail "plugin link is dangling (target does not exist): ${plugin_dir}"
}
# 4) Resolved target must be a real directory.
if [ ! -d "$resolved" ]; then
fail "plugin path does not resolve to a directory: ${plugin_dir} -> ${resolved}"
fi
# 5) DOGFOOD-008 guard: final target's basename must be the expected plugin
# name, so a typo pointing at an existing-but-wrong dir is caught.
base="$(basename "$resolved")"
if [ "$base" != "$plugin_name" ]; then
fail "plugin resolves to wrong directory: ${plugin_dir} -> ${resolved} (expected basename '${plugin_name}', got '${base}')"
fi
[ "$verbose" -eq 1 ] && printf 'SCHEDULER-VERIFY OK: %s -> %s\n' "$plugin_dir" "$resolved"
exit 0
Cron line (2h, as documented in the script header): 17 */2 * * * /fleet/deploy/scheduler-verify.sh
Tests (deploy/test-scheduler-verify.sh): every case builds an isolated fake home in a temp dir and runs the verifier with HOME overridden — the real user home and /fleet checkout are never touched.
Full suite: **9/9 passed, exit 0** (plus `bash -n` syntax clean on both scripts).
```
PASS positive: symlink -> real dir 'coding-hermes'
PASS positive: plain real dir named 'coding-hermes'
PASS positive: symlink chain resolves to 'coding-hermes' (link -> link -> real dir)
PASS positive: verbose mode exits 0 and prints OK line
PASS negative: typoed target .../coding (missing -hermes-), wrong dir exists
PASS negative: dangling symlink (target missing)
PASS negative: plugin path absent
PASS negative: symlink -> regular file
PASS negative: plain dir named 'coding' (wrong name)
```
Live repro of the original bug — a fake home where the symlink points at an *existing* `.../coding` (typo missing `-hermes-`): the verifier catches it loudly, exit 1:
```
$ HOME=/tmp/fake bash deploy/scheduler-verify.sh; echo exit=$?
SCHEDULER-VERIFY FAIL [2026-08-15T13:35:07-05:00] plugin resolves to wrong directory:
/tmp/fake/.hermes/plugins/coding-hermes -> /tmp/fake/fleet/plugins/coding
(expected basename 'coding-hermes', got 'coding')
exit=1
```
Edge cases exercised beyond the suite: positive case is silent with `stdout=''` and exit 0 (cron-friendly); `-v` prints `SCHEDULER-VERIFY OK: ... -> ...`; unknown argument exits 2; `HOME` unset now fails loudly ("HOME is not set; cannot locate coding-hermes plugin", exit 1) instead of a raw `set -u` traceback — this was a defect I found while testing and fixed by moving the guard above the `plugin_dir` assignment; empty/unlinked home fails with "plugin path missing".
Two test bugs were caught and fixed during development (missing `mkdir -p` for the plugins dir, and a symlink-chain test that pointed at a parent dir — the verifier correctly rejected basename `current`, proving check #5 works).{"model": "deepseek-v4-flash", "problem_class": "bash-deploy-verify-symlink-check", "result": "passed", "tests": 9}Root cause (DOGFOOD-008): ~/.hermes/plugins/coding-hermes is a symlink into the /fleet checkout. After a checkout move, its target path was typoed (missing -hermes-). Because nothing verified the link, the breakage went silent — the plugin just quietly pointed nowhere. The naive fix ("check it's a dir") is insufficient: a typo like .../coding can point at an existing but wrong directory, which passes any plain -d check. So the verifier checks three things: the path exists, it resolves to a real directory, and the fully-resolved target's basename is exactly coding-hermes.
deploy/scheduler-verify.sh (~/deploy/scheduler-verify.sh) — cron-friendly: silent exit 0 on success (no cron mail noise), loud structured SCHEDULER-VERIFY FAIL [timestamp] reason on stderr with exit 1 on any failure.
#!/usr/bin/env bash
set -u
plugin_name="${PLUGIN_NAME:-coding-hermes}"
verbose=0
case "${1:-}" in
-v|--verbose) verbose=1 ;;
"") ;;
*) printf 'SCHEDULER-VERIFY: unknown argument: %s\n' "$1" >&2; exit 2 ;;
esac
fail() {
printf 'SCHEDULER-VERIFY FAIL [%s] %s\n' "$(date -Is 2>/dev/null || date)" "$1" >&2
exit 1
}
if [ -z "${HOME:-}" ]; then
fail "HOME is not set; cannot locate ${plugin_name} plugin"
fi
plugin_dir="${HOME}/.hermes/plugins/${plugin_name}"
# 1) Path must exist at all (file, dir, or symlink).
if [ ! -e "$plugin_dir" ] && [ ! -L "$plugin_dir" ]; then
fail "plugin path missing: ${plugin_dir}"
fi
# 2) A regular file at the plugin path is never valid.
if [ -f "$plugin_dir" ] && [ ! -L "$plugin_dir" ]; then
fail "plugin path is a regular file, not a directory or symlink: ${plugin_dir}"
fi
# 3) Fully resolve. readlink -f fails (empty, non-zero) on dangling links.
resolved="$(readlink -f "$plugin_dir" 2>/dev/null)" || {
fail "plugin link is dangling (target does not exist): ${plugin_dir}"
}
# 4) Resolved target must be a real directory.
if [ ! -d "$resolved" ]; then
fail "plugin path does not resolve to a directory: ${plugin_dir} -> ${resolved}"
fi
# 5) DOGFOOD-008 guard: final target's basename must be the expected plugin
# name, so a typo pointing at an existing-but-wrong dir is caught.
base="$(basename "$resolved")"
if [ "$base" != "$plugin_name" ]; then
fail "plugin resolves to wrong directory: ${plugin_dir} -> ${resolved} (expected basename '${plugin_name}', got '${base}')"
fi
[ "$verbose" -eq 1 ] && printf 'SCHEDULER-VERIFY OK: %s -> %s\n' "$plugin_dir" "$resolved"
exit 0
Cron line (2h, as documented in the script header): 17 */2 * * * /fleet/deploy/scheduler-verify.sh
Tests (deploy/test-scheduler-verify.sh): every case builds an isolated fake home in a temp dir and runs the verifier with HOME overridden — the real user home and /fleet checkout are never touched.
Full suite: **9/9 passed, exit 0** (plus `bash -n` syntax clean on both scripts).
```
PASS positive: symlink -> real dir 'coding-hermes'
PASS positive: plain real dir named 'coding-hermes'
PASS positive: symlink chain resolves to 'coding-hermes' (link -> link -> real dir)
PASS positive: verbose mode exits 0 and prints OK line
PASS negative: typoed target .../coding (missing -hermes-), wrong dir exists
PASS negative: dangling symlink (target missing)
PASS negative: plugin path absent
PASS negative: symlink -> regular file
PASS negative: plain dir named 'coding' (wrong name)
```
Live repro of the original bug — a fake home where the symlink points at an *existing* `.../coding` (typo missing `-hermes-`): the verifier catches it loudly, exit 1:
```
$ HOME=/tmp/fake bash deploy/scheduler-verify.sh; echo exit=$?
SCHEDULER-VERIFY FAIL [2026-08-15T13:35:07-05:00] plugin resolves to wrong directory:
/tmp/fake/.hermes/plugins/coding-hermes -> /tmp/fake/fleet/plugins/coding
(expected basename 'coding-hermes', got 'coding')
exit=1
```
Edge cases exercised beyond the suite: positive case is silent with `stdout=''` and exit 0 (cron-friendly); `-v` prints `SCHEDULER-VERIFY OK: ... -> ...`; unknown argument exits 2; `HOME` unset now fails loudly ("HOME is not set; cannot locate coding-hermes plugin", exit 1) instead of a raw `set -u` traceback — this was a defect I found while testing and fixed by moving the guard above the `plugin_dir` assignment; empty/unlinked home fails with "plugin path missing".
Two test bugs were caught and fixed during development (missing `mkdir -p` for the plugins dir, and a symlink-chain test that pointed at a parent dir — the verifier correctly rejected basename `current`, proving check #5 works).{"model": "deepseek-v4-flash", "problem_class": "bash-deploy-verify-symlink-check", "result": "passed", "tests": 9}