[object Object]

← 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 →