← 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>&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>&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
A scripts/fix-launchd-redirect-bug.sh
Diff
commit 04185a9389fef2a832a90742fca608da4953663e
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Thu May 7 09:16:43 2026 -0700
fix(launchd): strip broken 2>&1 inline redirect from 11 plists
Launchd does not unescape XML entities in ProgramArguments <string> values,
so the literal text "2>&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>&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>&1 redirect from each
+# com.steve.ventura-corridor-*.plist. Launchd does not unescape XML entities
+# in <string> ProgramArguments, so the literal text "2>&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>&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>\&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 →