bundle check, bundle outdated), emit a short dependency diff summary, and alert Slack-style receivers through a generic HTTP webhook—without blocking the main CI farm.
In 2026, OpenClaw’s gateway, dashboard, and multi-channel routing patterns are converging on the same operational baseline: pin Node 22+, keep listeners healthy under launchd, and treat every inbound hook as untrusted until a shared secret or signature matches (docs.openclaw.ai).
Pair this guide with the Bundler cross-border matrix, GitHub check lockfile scan, and the public remote Mac build node guide plus help (no login required).
- Silent lock drift: CI queues a full matrix while
Gemfile.lockalready disagrees with declared gems. - Gateway fragility: without token discipline and health checks, webhooks hammer a dead port and operators lose trust in automation.
- Alert noise: dumping multi-megabyte
bundlelogs into chat channels hides the one line that matters.
Install OpenClaw, gateway user, tokens, and the daemon
Install Node 22 LTS (or Node 24 once openclaw doctor is clean on your build) for the dedicated macOS account that will own the gateway. Install OpenClaw with the official curl installer or npm install -g openclaw, run openclaw onboard, then iterate on openclaw doctor until warnings you care about are resolved.
Store webhook shared secrets, optional Git read tokens, and Bundler mirror credentials under ~/.config/openclaw/ or your team’s chosen secrets directory at mode 600. Export NODE_BINARY in the launchd plist so reboots do not fall back to an interactive-only nvm path.
| Control | Why it matters | Minimal pattern |
|---|---|---|
| Service account | Separates automation keys from developer laptops | Dedicated macOS user, no GUI auto-login, FileVault-aware reboot checklist |
| Listener bind | Reduces exposure of the Node process | Loopback HTTP upstream to nginx or Caddy TLS termination |
| Dashboard sanity | Catches route drift after upgrades | After onboard, confirm automation routes in the OpenClaw dashboard match your handler IDs |
Cross-link. For Jenkins-shaped triggers with composer or npm dry-runs, mirror the same secret-header pattern described in OpenClaw + Jenkins webhook preflight; this article swaps the middle tier for Bundler.
Gateway health: LaunchAgent probes and quick manual checks
Load the gateway with launchctl bootstrap (or your org’s wrapper) and prove the process survives a reboot. Schedule a lightweight LaunchAgent that runs curl -fsS against the documented health URL from docs.openclaw.ai; on non-zero exit, restart the gateway job and increment a metric or file log.
When debugging, replay one webhook with curl from the same host Jenkins or GitHub Actions will use, not only from your laptop VPN path. For LaunchAgent-specific patterns and paths, see gateway healthcheck LaunchAgent how-to.
# Example: hourly health (adjust URL to your local gateway health route) curl -fsS --max-time 8 "http://127.0.0.1:18789/health" || launchctl kickstart -k gui/$(id -u)/com.example.openclaw.gateway
Per-delivery workdir sandbox
Never mutate a single shared clone for concurrent CI events. Create WORKDIR=$(mktemp -d /var/tmp/openclaw-bundler.XXXXXX) (or an NVMe scratch mount), export GIT_TERMINAL_PROMPT=0, and shallow-clone the repository referenced in the webhook JSON. If you need Bundler to write gems, set BUNDLE_PATH="$WORKDIR/vendor/bundle" so installs stay inside the disposable tree.
Pass through REF or SHA fields from the payload; default to origin/main only when the sender guarantees that branch. Clean up with rm -rf "$WORKDIR" in a trap on EXIT.
Script template: bundle check, bundle outdated, and a diff summary
After cd "$WORKDIR/repo", run bundle _2.4.22_ version or the Bundler version recorded in Gemfile.lock so CI and gateway agree. Execute bundle check first—non-zero means missing gems or platform mismatch before you spend network budget. Then collect bundle outdated --parseable (adjust flags for your Bundler major) and count lines for the summary.
When the webhook includes a base SHA, optionally git fetch origin "$BASE_REF" and git diff "$BASE_SHA..HEAD" -- Gemfile.lock; truncate diff output to roughly four kilobytes for chat-friendly alerts. Serialize a single JSON object: ok, repo, ref, bundleCheckExit, outdatedLines, lockDiffExcerpt, durationMs.
#!/usr/bin/env bash
set -euo pipefail
WORKDIR=$(mktemp -d /var/tmp/openclaw-bundler.XXXXXX)
trap 'rm -rf "$WORKDIR"' EXIT
git clone --depth 20 "$GIT_URL" "$WORKDIR/app"
cd "$WORKDIR/app" && git checkout -q "$GIT_SHA"
export BUNDLE_PATH="$WORKDIR/vendor/bundle"
export BUNDLE_JOBS="${BUNDLE_JOBS:-6}"
export BUNDLE_RETRY="${BUNDLE_RETRY:-4}"
LOG="$WORKDIR/bundler.log"
bundle check >>"$LOG" 2>&1 || BC=$?
bundle outdated --parseable >>"$LOG" 2>&1 || true
# Build SUMMARY_JSON from $LOG + metadata (jq recommended)
# echo "$SUMMARY_JSON" | curl -sS --max-time 25 -H "Content-Type: application/json" \
# -H "Authorization: Bearer $WEBHOOK_SECRET" -d @- "$ALERT_URL"
If you route the same payload to several receivers, reuse the fan-out ideas in multi-endpoint CI summary routing.
Failure retry, log archival, and bounded runtimes
Wrap bundle check and optional bundle install --local probes in up to three attempts with sleeps of two, four, and eight seconds between failures—mirroring the retry posture we use for outbound CI summaries elsewhere on MacPull. Cap wall clock per attempt with timeout or Bundler-friendly environment knobs so a stuck rubygems path cannot wedged the gateway worker.
Archive bundler.log plus redacted environment metadata into ~/Library/Logs/openclaw-bundler/$(date +%Y%m%d-%H%M%S).tar.gz (path adjustable) before deleting the workdir. Operators can open the tarball while chat still receives only the short JSON excerpt.
For OpenClaw-native retry configuration and dependency-pull auto-retry concepts, read failure recovery and retry on remote Mac.
Generic HTTP webhook: POST the summary
Point ALERT_URL at any HTTPS endpoint that accepts application/json: Slack incoming-webhook compatible shapes, PagerDuty event bridges, or an internal aggregator. Keep payloads compact; move large artifacts to object storage and include only a URL token in the JSON if needed.
SUMMARY_JSON='{"source":"openclaw-bundler","ok":true,"repo":"acme/api","ref":"main","outdatedCount":3}'
for s in 2 4 8; do
if curl -fsS --max-time 25 -H "Content-Type: application/json" \
-H "Authorization: Bearer ${WEBHOOK_TOKEN}" \
-d "$SUMMARY_JSON" "$ALERT_URL"; then
break
fi
sleep "$s"
done
If your receiver expects vendor-specific keys, map fields in a tiny jq template—similar channel-specific notes appear in Discord webhook CI summary and Telegram and Slack FAQ.
Troubleshooting FAQ
bundle: command not found only under launchd
Your PATH is shorter than an interactive zsh session. Set absolute paths to ruby and bundle in the plist, or call /opt/homebrew/opt/ruby/bin/bundle explicitly.
bundle check succeeds but CI still cannot compile native extensions
Preflight never replaced your compiler matrix. Keep separate jobs for xcodebuild, bundle exec rake compile, or fat-binary gems.
Rotate the bearer token, confirm the header name matches the provider, and ensure no reverse proxy strips Authorization.
vendor/bundle trees
Lower retention, move BUNDLE_PATH to a deduplicated cache volume keyed by Gemfile.lock checksum, and schedule pruning LaunchAgents.
Summary
Order of operations: align Node and openclaw doctor, protect tokens, keep the gateway alive with health probes, isolate each hook in a fresh workdir, run Bundler check and outdated with a trimmed diff, retry transient failures, archive logs, and POST compact JSON to your HTTP webhook.
Need a machine that stays awake for this loop? Use public pages: home, purchase, regional guides such as US West, Japan, and South Korea, plus help and the blog—no sign-in required for those destinations.
MacPull rents Apple Silicon Mac Mini nodes so OpenClaw gateways, Bundler preflight, and webhook relays stay online 24/7.