Build Heartbeat Monitor
Overview
Wrap any long-running build command so its progress stays observable and its final state
is unambiguous. A background heartbeat appends a [HEARTBEAT] marker to a log file every
N seconds (refreshing the file's mtime) and prints elapsed time to the terminal; on exit
it writes a terminal marker (BUILD_EXIT=0 or [FAILED]). A separate monitor reader
inspects the log and reports RUNNING / STUCK / SUCCESS / FAILED / NOLOG. This
eliminates the "build silently died and nobody noticed for hours" failure mode common in
sandboxed or idle-session environments.
When To Use
Trigger this skill when:
- A build step is expected to take minutes to hours (Flutter/iOS/Android packaging, NDK
cross-compile, FreeRDP/OpenSSL cross-build, large
gradle/cmake/xcodebuildruns). - The environment has killed background tasks before (session idle reclaim, sandbox
resource limits that kill
flutter/dart/gradlewithout an exit code). - A previous attempt produced no log, an empty log, or a build that hung with no signal.
- When designing a one-click build script and wanting built-in observability + a monitor that a scheduled automation or a human can poll.
Do NOT use this for quick commands (< 30s) — the overhead is unnecessary.
How To Use
1. One-shot wrapper (recommended)
Source the library and wrap the build command in a single call:
source "$(dirname "$0")/heartbeat.sh" # or the skill's scripts/ path
LOG=build_run.log
run_with_heartbeat "$LOG" 60 flutter build hap --release
# exit code 0 = success (BUILD_EXIT=0 written); non-zero = failed ([FAILED] written)
run_with_heartbeat <logfile> <interval_sec> <cmd...> truncates the log (via : >,
NOT rm), starts the heartbeat, tees the build output to the log, stops the heartbeat,
and writes the terminal marker.
2. Manual start/stop (embed in an existing script)
source heartbeat.sh
start_heartbeat build_run.log 60 # launches bg loop + registers EXIT/INT/TERM trap
<your long command> 2>&1 | tee -a build_run.log
stop_heartbeat # trap also cleans up on script exit
3. Monitor / poll status
bash monitor_build.sh build_run.log --stuck-minutes 3
# outputs one line: NOLOG | SUCCESS | FAILED | RUNNING | STUCK, with exit codes 3/0/1/0/2
For live observation, run tail -f build_run.log in another window, or wire
monitor_build.sh into a scheduled automation that alerts on STUCK (exit 2).
4. Integration into a one-click build script
Combine with environment setup (NODE_HOME, SDK paths, signing) as shown in
references/pattern.md §四. The heartbeat is orthogonal to deployment: it only makes the
build observable; device install/verify is a separate stage.
Critical Constraints (sandbox-safe)
These come from repeated failures and MUST be preserved:
- Truncate logs with
: > file, neverrm. A safe-delete shim interceptsrmand rename-over-existing, which would abort the command chain and make the log vanish. - Append markers with
>>, never overwrite. - Track the heartbeat subprocess by PID and make
stopidempotent (kill -0beforekill). - Register an EXIT/INT/TERM trap so the terminal marker is always written, even on Ctrl-C or unexpected exit.
- Use
tee -aso build output reaches both terminal and log without disturbing the heartbeat's mtime refresh.
Resources
scripts/heartbeat.sh
Bash function library. Provides run_with_heartbeat, start_heartbeat, stop_heartbeat.
Source it, then call run_with_heartbeat <log> <interval> <cmd...>. Implements the
log protocol and all sandbox-safe constraints above.
scripts/monitor_build.sh
Standalone status reader. monitor_build.sh <logfile> [--stuck-minutes N] [--quiet].
Prints one status line and uses exit codes: 0=SUCCESS/RUNNING, 1=FAILED, 2=STUCK, 3=NOLOG.
references/pattern.md
Full rationale: why builds silently die, the log protocol table, the sandbox-safe implementation constraints, copy-paste integration templates, and a cross-project adaptation checklist (interval / STUCK threshold / CI wiring).
Scan to join WeChat group