| name | cron-postflight |
| description | Cron-fire post-flight scaffolding. Audit-log INSERT, git commit + push, notification send. Replaces inline wrap-up boilerplate across scheduled tasks. |
| version | 1.0.0 |
| author | Bubble Invest |
| license | MIT |
| allowed-tools | ["Bash","Read"] |
Cron post-flight — shared scaffolding
Every scheduled task tends to end with the same 3 chores:
- Write an
audit_log row recording what happened (actor, action, target, outcome)
- Commit + push any repo state mutated this run
- Send a notification with the run summary
This skill provides parametric helpers for all three.
Notification routing comes from env vars so this skill ships clean. Set
OPERATOR_CHAT_ID and a token file path before using Block 3.
When to invoke
In any cron's wrap-up STEP. Each helper is a separate Block — invoke whichever you need; you don't have to use all 3.
Block 1 — Audit log row
sqlite3 "${POSTFLIGHT_DB:?must set POSTFLIGHT_DB}" \
"INSERT INTO audit_log (logged_at, actor, action, target, details, outcome)
VALUES (
strftime('%Y-%m-%dT%H:%M:%fZ','now'),
'${POSTFLIGHT_ACTOR:?must set POSTFLIGHT_ACTOR}',
'${POSTFLIGHT_ACTION:?must set POSTFLIGHT_ACTION}',
'${POSTFLIGHT_TARGET:?must set POSTFLIGHT_TARGET}',
'${POSTFLIGHT_DETAILS:-}',
'${POSTFLIGHT_OUTCOME:-success}'
);"
Block 2 — Git commit + push
cd "${POSTFLIGHT_GIT_WORKDIR:?must set POSTFLIGHT_GIT_WORKDIR}"
echo "[postflight] git status (before commit):"
git status --short
if [ -n "${POSTFLIGHT_GIT_LANE:-}" ]; then
OFFENDING=$(git status --short | awk '{print $2}' | grep -v "^${POSTFLIGHT_GIT_LANE}" | head -3)
if [ -n "$OFFENDING" ]; then
echo "[postflight] git LANE breach — changes outside ${POSTFLIGHT_GIT_LANE}: $OFFENDING"
echo "[postflight] aborting commit; investigate the lane crossing manually"
exit 1
fi
fi
git add ${POSTFLIGHT_GIT_PATHSPEC:--A}
if git diff --cached --quiet; then
echo "[postflight] no staged changes — skipping commit (no-op)"
COMMIT_OUTCOME="noop"
else
git commit -m "${POSTFLIGHT_GIT_MESSAGE:?must set POSTFLIGHT_GIT_MESSAGE}"
COMMIT_OUTCOME="committed"
fi
if [ "${POSTFLIGHT_GIT_PUSH:-yes}" = "yes" ] && [ "$COMMIT_OUTCOME" = "committed" ]; then
git push origin main 2>&1 || echo "[postflight] push failed — local commit intact"
fi
Block 3 — Notification
BOT_TOKEN=$(cat "${TELEGRAM_BOT_TOKEN_FILE:?set TELEGRAM_BOT_TOKEN_FILE}")
CHAT_ID="${OPERATOR_CHAT_ID:?set OPERATOR_CHAT_ID}"
BODY="${POSTFLIGHT_TG_BODY:?must set POSTFLIGHT_TG_BODY}"
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${CHAT_ID}" \
--data-urlencode "text=${BODY}"
Note on Markdown rendering: Block 3 sends as plain text — no parse_mode. Asterisks, underscores, backticks render literally. If you need Markdown rendering, use a JSON-POST pattern with parse_mode: Markdown instead — but then every caller must escape special chars, which is usually more friction than it's worth.
For attachments (chart PNGs etc.), use sendPhoto instead:
BOT_TOKEN=$(cat "$TELEGRAM_BOT_TOKEN_FILE")
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendPhoto" \
-F chat_id="$OPERATOR_CHAT_ID" \
-F photo=@/tmp/brief_chart.png \
-F caption="<one-line caption>"
What the calling cron sees
Each block is independent — they don't communicate via env vars. After each successful block, the calling cron simply continues. After a failed block (e.g. git lane breach), exit code is non-zero — the cron should not assume Block N+1 ran.
Hard invariants
- DB writes only via Block 1: keep a single audit-trail discipline — never INSERT to your DB from elsewhere in the post-flight phase.
- Notification routing: all chat IDs and tokens come from env vars.
- Git push is non-fatal: a failed
git push origin main does NOT fail the cron; local commit stands and the next run retries the push.
- Lane discipline (Block 2): when
POSTFLIGHT_GIT_LANE is set, refuses to commit anything outside that path — prevents a cron crossing perimeter.