← back to Claude Codex Meeting
auto-act: tolerate ## or ### heading depth + grep-empty (pipefail crash); now handles meeting-format drift cleanly
0f820e8bbda3276ef251feef482207c114c297d7 · 2026-05-08 00:08:49 -0700 · Steve
Files touched
Diff
commit 0f820e8bbda3276ef251feef482207c114c297d7
Author: Steve <steve@designerwallcoverings.com>
Date: Fri May 8 00:08:49 2026 -0700
auto-act: tolerate ## or ### heading depth + grep-empty (pipefail crash); now handles meeting-format drift cleanly
---
auto-act.sh | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 108 insertions(+)
diff --git a/auto-act.sh b/auto-act.sh
new file mode 100755
index 0000000..5145c52
--- /dev/null
+++ b/auto-act.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+#
+# Auto-act on the most recent Claude-Codex meeting report.
+# Fires from launchd at 9:10 PT and 17:10 PT (10 min after each meeting drops).
+#
+# Workflow:
+# 1. Read last-meeting.md
+# 2. Extract Section 3 "Today's Plan + Agent Assignments" — backtick-quoted lines
+# 3. For each `<agent-name> — <assignment>`:
+# - if agent is "Steve" → queue for Slack TODOs
+# - else → spawn `claude -p "<assignment>" --agent <agent-name>` in background
+# 4. Post a single Slack notification summarizing what fired + what's queued for Steve
+# 5. Idempotency: skip if .last-acted matches the current meeting filename
+#
+# Slack:
+# Reads SLACK_WEBHOOK_URL from env (route via /secrets when Steve provides it).
+# Falls back to George email if SLACK_WEBHOOK_URL is unset.
+#
+set -euo pipefail
+cd /Users/stevestudio2/Projects/claude-codex-meeting
+
+DRY_RUN=0
+[ "${1:-}" = "--dry-run" ] && DRY_RUN=1
+
+CLAUDE_BIN="/Users/stevestudio2/.local/bin/claude"
+LOG_DIR=logs
+mkdir -p "$LOG_DIR"
+LOG="$LOG_DIR/auto-act-$(date -u +%Y%m%d).log"
+
+log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG" ; }
+
+MEETING=$(readlink last-meeting.md 2>/dev/null || echo last-meeting.md)
+MEETING_NAME=$(basename "$MEETING")
+LAST_ACTED_FILE=.last-acted
+
+if [ -f "$LAST_ACTED_FILE" ] && [ "$(cat "$LAST_ACTED_FILE")" = "$MEETING_NAME" ]; then
+ log "skip — already acted on $MEETING_NAME"
+ exit 0
+fi
+
+log "acting on meeting: $MEETING_NAME"
+
+# Parse the agent-assignments section. Tolerate either heading depth (## or ###),
+# either Section 3 or Section 4 (meeting format drifted 2026-05). Use `|| true`
+# on the grep so an empty section doesn't trip pipefail and silent-crash.
+SECTION3=$( {
+ awk '/^#### 3\./,/^#### 4\./' last-meeting.md
+ awk '/^### 3\./,/^### 4\./' last-meeting.md
+ awk '/^## 3\./,/^## 4\./' last-meeting.md
+ awk '/^### 4\./,/^### 5\./' last-meeting.md
+ awk '/^## 4\./,/^## 5\./' last-meeting.md
+} | grep -E '^\s*- `' | sed -E 's/^\s*- `(.*)`\s*$/\1/' | sort -u || true)
+
+if [ -z "$SECTION3" ]; then
+ log "no Section 3 assignments found — nothing to do"
+ echo "$MEETING_NAME" > "$LAST_ACTED_FILE"
+ exit 0
+fi
+
+log "extracted $(echo "$SECTION3" | wc -l | tr -d ' ') assignments"
+
+STEVE_TODOS=""
+AGENT_FIRED=0
+AGENT_LIST=""
+
+while IFS= read -r line; do
+ [ -z "$line" ] && continue
+ AGENT=$(echo "$line" | sed -E 's/^([^ —]+).*/\1/')
+ TASK=$(echo "$line" | sed -E 's/^[^—]+— *(.*)$/\1/')
+ if [ "$AGENT" = "Steve" ]; then
+ STEVE_TODOS="${STEVE_TODOS}• ${TASK}\n"
+ log "queued for Steve: $TASK"
+ else
+ AGENT_FIRED=$((AGENT_FIRED + 1))
+ AGENT_LIST="${AGENT_LIST}• ${AGENT}: ${TASK}\n"
+ if [ "$DRY_RUN" = "1" ]; then
+ log "DRY-RUN would fire agent=$AGENT task='$TASK'"
+ else
+ log "firing agent=$AGENT task='$TASK'"
+ nohup "$CLAUDE_BIN" -p "$TASK" --agent "$AGENT" \
+ > "$LOG_DIR/agent-${AGENT}-$(date -u +%Y%m%dT%H%M%SZ).log" 2>&1 &
+ fi
+ fi
+done <<< "$SECTION3"
+
+# Build Slack message
+TIME_LABEL=$([ "$(date +%H)" -lt 12 ] && echo morning || echo evening)
+SLACK_TEXT="*Codex meeting auto-act fired* — ${TIME_LABEL} report ($MEETING_NAME)\n\n*Agents spawned ($AGENT_FIRED):*\n${AGENT_LIST}\n*Steve TODOs:*\n${STEVE_TODOS:-_(none)_}"
+
+if [ "$DRY_RUN" = "1" ]; then
+ log "DRY-RUN skipping notification (would have posted: $SLACK_TEXT)"
+elif [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
+ log "posting to Slack"
+ PAYLOAD=$(printf '{"text":"%s"}' "${SLACK_TEXT//\"/\\\"}")
+ curl -s -X POST -H 'Content-Type: application/json' --data "$PAYLOAD" "$SLACK_WEBHOOK_URL" >> "$LOG" 2>&1 || log "slack post failed"
+else
+ log "no SLACK_WEBHOOK_URL — falling back to George email"
+ BODY="<p>Codex meeting auto-act fired — ${TIME_LABEL} report (${MEETING_NAME})</p><p><b>Agents spawned (${AGENT_FIRED}):</b><br>$(echo -e "$AGENT_LIST" | sed 's/$/<br>/')</p><p><b>Steve TODOs:</b><br>$(echo -e "${STEVE_TODOS:-(none)}" | sed 's/$/<br>/')</p>"
+ printf '{"to":"steveabramsdesigns@gmail.com","subject":"[codex auto-act] %s — %d agents fired","body":"%s"}' \
+ "$TIME_LABEL" "$AGENT_FIRED" "${BODY//\"/\\\"}" > /tmp/codex-act-payload.json
+ curl -s -X POST 'http://localhost:9850/api/send?account=info' \
+ -H 'Authorization: Basic YWRtaW46RFdTZWN1cmUyMDI0IQ==' \
+ -H 'Content-Type: application/json' \
+ --data-binary @/tmp/codex-act-payload.json >> "$LOG" 2>&1 || log "george email failed"
+fi
+
+[ "$DRY_RUN" = "0" ] && echo "$MEETING_NAME" > "$LAST_ACTED_FILE"
+log "done — $AGENT_FIRED agents fired, $(echo -e "$STEVE_TODOS" | grep -c '•') Steve TODOs"
← 387033c tighten .gitignore: add missing standing-rule patterns (.env
·
back to Claude Codex Meeting
·
codex-meeting · hook cost-tracker for call-count attribution 4cc9f28 →