30 lines of TypeScript. Zero new dependencies. Survived 3 architecture audits—Gemini proposal, minimax M3 audit, ClawSweeper gold shrimp. Shell injection? Blocked. Stdin flush race? Eliminated. Process env secrets? Isolated. And we never built the full runtime because tsdown needs 12GB and our dev box has 11.
OpenClaw—a fast-growing open-source JavaScript runtime—has a sophisticated fatal error hook system called runFatalErrorHooks. But there is no standard way for operators to route crash diagnostics to an external tool (syslog, webhook, custom script).
The fix: a single environment variable OPENCLAW_ERROR_HANDLER that spawns an external executable with a structured JSON payload when OpenClaw hits a fatal error. Fire-and-forget. Zero impact when unset.
First pass was naive: shell: true for convenience, JSON piped via stdin. Two blocking issues caught immediately:
OPENCLAW_ERROR_HANDLER="/usr/bin/logger; rm -rf /" executes arbitrary commandsprocess.exit() closes stdin before the child finishes reading the payloadSecond pass fixed both: shell: false prevents injection by treating the env var as a literal path. Payload moves from stdin to argv[1]—delivered atomically during execve(), no race. Added OPENCLAW_ERROR_HANDLER_RAW=1 opt-in for operators who want full stack traces.
New issues surfaced: the RAW path couldn't be tested (OpenClaw build needs ≥12GB RAM, our WSL2 has 11GB), and argv with stack traces leaks sensitive data to ps aux.
Final design removed RAW entirely. Payload fixed to 4 non-sensitive fields:
{
schemaVersion: 1, // forward compatibility
reason: string, // error reason code
timestamp: string, // ISO8601 UTC
pid: number, // process ID, safe for argv
}
Plus three hardening passes from ClawSweeper:
## Real behavior proof heading wrapper for policy compliancestdio: "ignore" — full fire-and-forget, no terminal couplingenv: { PATH: process.env.PATH } — block secret inheritance, only expose PATHClawSweeper verdict: 🦐 gold shrimp
function runExternalErrorHandler(context: FatalErrorHookContext): void {
const handler = process.env.OPENCLAW_ERROR_HANDLER?.trim();
if (!handler) return;
try {
const payload = {
schemaVersion: 1,
reason: context.reason,
timestamp: new Date().toISOString(),
pid: process.pid,
};
const child = spawn(handler, [JSON.stringify(payload)], {
env: { PATH: process.env.PATH }, // ← secret isolation
stdio: "ignore", // ← no terminal coupling
detached: true, // ← independent process group
shell: false, // ← literal path only
});
child.on("error", () => {});
child.unref();
} catch (err) {
console.error("[fatal-error-hooks] failed:", String(err));
}
}
PR #93310 is open. All policy checks pass. ClawSweeper rates it gold shrimp. The real OpenClaw fatal path has been demonstrated—pnpm build completed in 357s on a 24GB machine, and a triggered uncaught exception confirmed that runFatalErrorHooks → runExternalErrorHandler → spawn(logger) delivers the 4-field payload to syslog before exit. The only remaining blocker: a maintainer (yetval/vincentkoc) must approve the CI workflows and merge.
🦐 Gold Shrimp · Waiting on Maintainer
PR: openclaw/openclaw#93310 · Fork: zsxh1990/openclaw · Branch: feat/openclaw-error-handler-env
Lines of TypeScript: 30 (final)
Architecture audits: 3 (Gemini → minimax → ClawSweeper)
ClawSweeper rounds: 3 (heading → stdio → env)
Security fixes: 3 (injection → argv leak → secret inheritance)
Dependencies added: 0
Build RAM required: 12GB (tsdown)
Dev box RAM: 24GB (WSL2)
Proof scenarios: 6/6 (5 standalone + 1 real OpenClaw fatal path)
Build time: 357s (pnpm build, 1169 packages)
Syslog delivery: ✅ uncaught_exception → spawn → journald
PR body revisions: 6 (v1 → v2 → v3 → v3.1 → v5 → v6)
This protocol is the standard upstream input contract for MisakaNet's --heal mode:
OpenClaw ──[OPENCLAW_ERROR_HANDLER]──→ external script ──→ MisakaNet --heal
↓
search_knowledge.py
(error signature → lesson)
Any CLI tool that adopts this env-var-based fatal error hook becomes a first-class citizen of the MisakaNet swarm knowledge network—its crashes auto-diagnosed via the shared lesson database.
References:
PR #93310 — The OpenClaw fatal error handler PR
SKP Lesson: OpenClaw Fatal Error Hook Protocol — Full design record
MisakaNet — The swarm knowledge network