diff --git a/bridged/src/main/java/dev/ltms/bridged/Bridged.java b/bridged/src/main/java/dev/ltms/bridged/Bridged.java
index fed7b66..c65eac1 100644
--- a/bridged/src/main/java/dev/ltms/bridged/Bridged.java
+++ b/bridged/src/main/java/dev/ltms/bridged/Bridged.java
@@ -555,7 +555,10 @@ public final class Bridged {
* where set (opt-in). Derived from the config, not hard-coded, so a new profile is covered for
* free. A var required by more than one profile is one entry naming every profile that needs
* it. Deliberately excludes {@code auth.tokenEnv}: that one is already enforced loudly, by a
- * startup throw, a few lines above this method's call site.
+ * startup throw in {@code main()} — about 370 lines below this method's call site
+ * ({@link #reportRequiredSecrets(BridgedConfig)}), not a few lines above it. That throw only
+ * fires when {@code auth.mode: token} is configured; under the default loopback-trust mode it
+ * never runs, and {@code auth.tokenEnv} is simply not required.
*
*
Package-private and pure (no I/O, no logging) so the derivation is unit-testable without
* capturing log output; {@link #reportRequiredSecrets(BridgedConfig)} is the logging caller.
diff --git a/deploy/dev.ltms.bridged.plist b/deploy/dev.ltms.bridged.plist
index 073e138..6666d8d 100644
--- a/deploy/dev.ltms.bridged.plist
+++ b/deploy/dev.ltms.bridged.plist
@@ -82,8 +82,25 @@
RunAtLoad
-
+
KeepAlive
SuccessfulExit
diff --git a/scripts/redeploy-bridged.sh b/scripts/redeploy-bridged.sh
index a5622e7..96ad365 100755
--- a/scripts/redeploy-bridged.sh
+++ b/scripts/redeploy-bridged.sh
@@ -79,6 +79,53 @@ running_pid() { pgrep -f "$PATTERN" || true; }
launchd_installed() { [ -f "$LAUNCHD_PLIST" ]; }
launchd_loaded() { launchctl list "$LAUNCHD_LABEL" >/dev/null 2>&1; }
+# CB-600: the script computes its own log path from where it sits on disk (REPO, above); the
+# plist hard-codes an absolute StandardOutPath. Nothing forced the two to agree — if this script
+# were ever run from a checkout other than the one the loaded plist names, launchd would start and
+# log the daemon correctly, while every check below (the fresh "bridged listening" line, the
+# ERROR-count scan) would read a different, empty or stale file and the script would report a
+# clean restart while the daemon crash-loops. Pure and side-effect-free besides `die`/`ok` — reads
+# the two paths, resolves them, compares — so it never touches launchd or the daemon and can be
+# exercised by sourcing this script (see the SOURCED guard below) without installing the agent.
+check_log_path_matches_plist() {
+ local script_out="$1" plist_path="$2"
+ local plist_out resolved_out resolved_plist_out
+ # Checked by exit status, not by emptiness: on a missing file/key PlistBuddy exits nonzero but
+ # still writes a message ("File Doesn't Exist, Will Create: ...") that command substitution
+ # would happily capture as if it were the real value — testing only `-z` missed that case.
+ if ! plist_out="$(/usr/libexec/PlistBuddy -c 'Print :StandardOutPath' "$plist_path" 2>/dev/null)" \
+ || [ -z "$plist_out" ]; then
+ die "launchd agent is loaded but PlistBuddy could not read StandardOutPath from
+ $plist_path
+ — cannot verify the daemon logs where this script is about to look. Fix the plist before
+ redeploying supervised."
+ fi
+ resolved_out="$(cd "$(dirname "$script_out")" 2>/dev/null && pwd -P)/$(basename "$script_out")" || true
+ resolved_plist_out="$(cd "$(dirname "$plist_out")" 2>/dev/null && pwd -P)/$(basename "$plist_out")" || true
+ if [ -z "$resolved_out" ] || [ -z "$resolved_plist_out" ] || [ "$resolved_out" != "$resolved_plist_out" ]; then
+ die "log path mismatch — this script reads
+ $script_out (resolved: ${resolved_out:-})
+ but the loaded plist's StandardOutPath is
+ $plist_out (resolved: ${resolved_plist_out:-})
+ Under supervision the daemon writes to the PLIST's path, not necessarily this script's — every
+ post-restart check below (the fresh 'bridged listening' line, the ERROR-count scan) would read
+ the wrong file and could report a clean restart while the daemon crash-loops. Fix the mismatch
+ (move this checkout to match the plist, or edit the plist's StandardOutPath/StandardErrorPath)
+ before redeploying supervised."
+ fi
+ ok "log path check: script and plist agree ($resolved_out)"
+}
+
+# CB-600: sourceable for testing. When this file is SOURCED (not executed) it stops here — nothing
+# below runs — so a test harness can `source` it to call check_log_path_matches_plist (or the
+# other pure helpers above) against a throwaway plist fixture without ever reaching the mutating
+# flow (build/stop/start) or touching the real daemon or launchd. On a normal `./redeploy-bridged.sh`
+# invocation `(return 0 2>/dev/null)` fails (return is illegal at top level of an executed script),
+# so this whole block is a no-op and every line below still runs exactly as before.
+if (return 0 2>/dev/null); then
+ return 0
+fi
+
# ---------------------------------------------------------------- report state
say "current state"
@@ -103,6 +150,9 @@ SUPERVISED=0
if launchd_loaded; then
SUPERVISED=1
ok "launchd agent loaded ($LAUNCHD_LABEL) — launchd supervises this daemon"
+ # CB-600: fail loudly here, before ANY other check runs, if this script and the loaded plist
+ # would read different log files — every check after this point is worthless otherwise.
+ check_log_path_matches_plist "$OUT" "$LAUNCHD_PLIST"
else
warn "launchd agent not loaded — this script is the only thing that will restart the daemon."
fi
@@ -222,7 +272,22 @@ say "start"
if [ "$SUPERVISED" = 1 ]; then
echo " supervision is ON: using 'launchctl load' so launchd starts and keeps supervising this"
echo " process, instead of a manual nohup that launchd would know nothing about."
- launchctl load -w "$LAUNCHD_PLIST" || die "launchctl load failed"
+ # CB-600: 'launchctl unload -w' above already persisted Disabled=true for this label. A load -w
+ # that succeeds clears it; a load -w that FAILS leaves the agent both stopped and disabled — worse
+ # than before this script ran, because a later reboot or login will not bring it back either. One
+ # retry covers a transient race (e.g. launchd not yet fully done deregistering); if it still fails,
+ # die with the exact recovery command rather than a bare "failed".
+ if ! launchctl load -w "$LAUNCHD_PLIST" 2>/dev/null; then
+ warn "launchctl load failed on the first attempt — retrying once after a short pause"
+ sleep 2
+ launchctl load -w "$LAUNCHD_PLIST" || die "launchctl load failed twice.
+ The agent is now STOPPED and DISABLED — it will NOT come back on its own, not even after a
+ reboot or login, because 'launchctl unload -w' above persisted Disabled=true and load -w
+ never got the chance to clear it. Recover with:
+ launchctl load -w \"$LAUNCHD_PLIST\"
+ If that still fails, check 'launchctl list $LAUNCHD_LABEL', validate the plist with
+ 'plutil -lint \"$LAUNCHD_PLIST\"', and check $OUT before assuming a retry will succeed."
+ fi
else
# Absolute jar path so `ps` names which checkout is running.
( cd "$BRIDGED" && zsh -lc "nohup java -jar '$JAR' >> bridged.out 2>&1 &" )