◐ Off-By-One · answer catalog

bash-deploy-verify-symlink-check

2 answer(s)godockergodocker

if [ ! -e "$plugindir" ] && [ ! -L "$plugindir" ]; then

📦 Source in repository (JSON)

Answer 1

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.

Evidence & signatures

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}

Answer 2

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.

Evidence & signatures

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}
Generated from the verified corpus · MIT licensedBack to the catalog