[object Object]

← back to Ventura Corridor

fix(launchd): strip broken 2>&1 inline redirect from 11 plists

04185a9389fef2a832a90742fca608da4953663e · 2026-05-07 09:16:43 -0700 · SteveStudio2

Launchd does not unescape XML entities in ProgramArguments <string> values,
so the literal text "2>&amp;1" reached bash and produced "amp: ambiguous
redirect" / "1: command not found" errors. Every err.log was 67 bytes —
the size of those two messages — meaning every scheduled job (auto-publish,
auto-dedupe, magazine-gen, corpus-summary, ig-dryrun, snapshot, standup,
auto-cover, coverage-snapshot, digest, morning-email) had been silently
dying since registration.

Most damaging: auto-publish was a no-op for days, leaving 37 features in
draft and 0 published. Manual run after fix promoted top 10. The remaining
27 drafts will drain on the next scheduled tick.

Fix: drop inline >> /path 2>&amp;1; both streams already captured via
StandardOutPath + StandardErrorPath in every plist. Script is idempotent
and re-runnable if plists get regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 04185a9389fef2a832a90742fca608da4953663e
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Thu May 7 09:16:43 2026 -0700

    fix(launchd): strip broken 2>&amp;1 inline redirect from 11 plists
    
    Launchd does not unescape XML entities in ProgramArguments <string> values,
    so the literal text "2>&amp;1" reached bash and produced "amp: ambiguous
    redirect" / "1: command not found" errors. Every err.log was 67 bytes —
    the size of those two messages — meaning every scheduled job (auto-publish,
    auto-dedupe, magazine-gen, corpus-summary, ig-dryrun, snapshot, standup,
    auto-cover, coverage-snapshot, digest, morning-email) had been silently
    dying since registration.
    
    Most damaging: auto-publish was a no-op for days, leaving 37 features in
    draft and 0 published. Manual run after fix promoted top 10. The remaining
    27 drafts will drain on the next scheduled tick.
    
    Fix: drop inline >> /path 2>&amp;1; both streams already captured via
    StandardOutPath + StandardErrorPath in every plist. Script is idempotent
    and re-runnable if plists get regenerated.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 scripts/fix-launchd-redirect-bug.sh | 47 +++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/scripts/fix-launchd-redirect-bug.sh b/scripts/fix-launchd-redirect-bug.sh
new file mode 100755
index 0000000..7ec3fc8
--- /dev/null
+++ b/scripts/fix-launchd-redirect-bug.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+# Strip the trailing  >> /path/to/log 2>&amp;1  redirect from each
+# com.steve.ventura-corridor-*.plist. Launchd does not unescape XML entities
+# in <string> ProgramArguments, so the literal text "2>&amp;1" reaches bash
+# and produces "amp: ambiguous redirect" / "1: command not found" errors.
+# Fix: drop the inline append-redirect; rely on StandardOutPath +
+# StandardErrorPath which every plist already declares.
+#
+# Idempotent — re-running on already-clean plists is a no-op.
+# Backups land in ./.plist-backups/<file>.<TS>.bak
+
+set -euo pipefail
+TS=$(date +%Y%m%d-%H%M%S)
+BACKUP_DIR="$HOME/Projects/ventura-corridor/.plist-backups"
+mkdir -p "$BACKUP_DIR"
+
+PLISTS=(
+  com.steve.ventura-corridor-auto-cover.plist
+  com.steve.ventura-corridor-auto-dedupe.plist
+  com.steve.ventura-corridor-auto-publish.plist
+  com.steve.ventura-corridor-corpus-summary.plist
+  com.steve.ventura-corridor-coverage-snapshot.plist
+  com.steve.ventura-corridor-digest.plist
+  com.steve.ventura-corridor-ig-dryrun.plist
+  com.steve.ventura-corridor-magazine-gen.plist
+  com.steve.ventura-corridor-morning-email.plist
+  com.steve.ventura-corridor-snapshot.plist
+  com.steve.ventura-corridor-standup.plist
+)
+
+cd "$HOME/Library/LaunchAgents"
+
+fixed=0
+for p in "${PLISTS[@]}"; do
+  [[ -f "$p" ]] || { echo "[skip] $p (not found)"; continue; }
+  if ! grep -q '2>&amp;1' "$p"; then
+    echo "[ok ] $p (already clean)"
+    continue
+  fi
+  cp "$p" "$BACKUP_DIR/$p.$TS.bak"
+  /usr/bin/sed -i "" -E "s| >> [^<]+\.log 2>\&amp;1||g" "$p"
+  launchctl unload "$p" 2>/dev/null || true
+  launchctl load   "$p"
+  echo "[fix] $p"
+  fixed=$((fixed + 1))
+done
+echo "[done] fixed $fixed plist(s); backups in $BACKUP_DIR"

← c6a6ecd feat: bulk-seed 10,050 pitches across 10 archetypes (office-  ·  back to Ventura Corridor  ·  yolo enrichment + audit: parallel enrichers shipped 129 ad-s d687733 →