openclaw - 💡(How to fix) Fix Feature: pre-run script hook/gate for heartbeat and cron agent triggers [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#49339Fetched 2026-04-08 00:55:56
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Participants

Code Example

{
  "triggers": {
    "heartbeat": {
      "interval": "30m",
      "preHook": {
        "command": "/home/user/scripts/check-for-new-emails.sh",
        "timeout": 30,
        "allowlist": ["/home/user/scripts/check-for-new-emails.sh"],
        "onExit": {
          "0": "continue",
          "1": "skip",
          "*": "fail"
        },
        "capture": {
          "stdout": true,
          "stderr": true,
          "outputPath": "/tmp/heartbeat-prehook-output.txt"
        }
      }
    }
  }
}
RAW_BUFFERClick to expand / collapse

Problem Statement

PR #17529 introduced a preCheck gate for cron jobs that runs a lightweight shell command before the agent turn, skipping the job if the command exits non-zero or produces empty stdout. This prevents wasteful token consumption on recurring cron jobs when there is nothing new to act on.

However, heartbeat agent triggers lack equivalent pre-run hook support. Heartbeat-triggered agents wake up on a schedule regardless of whether there is meaningful work to do, leading to the same token-waste problem that the cron preCheck gate solves.

Why This Is Needed

  1. Token Efficiency: Both heartbeat and cron triggers should gate agent activation based on pre-run conditions
  2. Consistency: A unified pre-hook mechanism across all scheduled triggers simplifies configuration
  3. Flexibility: Users want to run arbitrary pre-check scripts to decide whether an agent should activate
  4. Extensibility: A general pre-hook framework enables future enhancements like conditional output capture and multi-step gates

Proposed Config Shape

{
  "triggers": {
    "heartbeat": {
      "interval": "30m",
      "preHook": {
        "command": "/home/user/scripts/check-for-new-emails.sh",
        "timeout": 30,
        "allowlist": ["/home/user/scripts/check-for-new-emails.sh"],
        "onExit": {
          "0": "continue",
          "1": "skip",
          "*": "fail"
        },
        "capture": {
          "stdout": true,
          "stderr": true,
          "outputPath": "/tmp/heartbeat-prehook-output.txt"
        }
      }
    }
  }
}

Exit Code Mapping

The onExit field maps exit codes to trigger behavior:

  • continue: Proceed with agent activation (pre-hook passed)
  • skip: Skip this trigger cycle entirely (status: skipped, no tokens consumed)
  • fail: Mark trigger as failed (status: failed, may alert/notify)

Default mapping (if onExit not specified):

  • Exit 0 → continue
  • Exit non-zero → skip

Timeout

  • Default: 30 seconds
  • Configurable per hook
  • On timeout: treat as fail (or configurable via onExit)

stdout/stderr Capture

  • capture.stdout: boolean, capture stdout output
  • capture.stderr: boolean, capture stderr output
  • capture.outputPath: optional file path to write captured output
  • Captured stdout can be injected as context into the agent turn when continue is chosen

Security / Allowlist Consistency

  • Hooks must reference executables in a configured allowlist (absolute paths only)
  • Prevents arbitrary command injection through config
  • Consistent with existing exec approval/allowlist patterns in OpenClaw
  • Suggested default allowlist: /bin/bash, /usr/bin/bash, and user-script directories

Optional outputPath

  • If specified, captured stdout/stderr are written to this file path
  • Useful for debugging, auditing, or passing output to other tools
  • Path must be writable by the OpenClaw process

Acceptance Criteria

  • preHook field added to heartbeat trigger config schema
  • preHook field added to cron job config schema (extending existing preCheck)
  • Pre-hook execution happens before agent activation/token consumption
  • Exit code mapping (continue/skip/fail) implemented and configurable
  • Timeout enforcement with configurable duration (default 30s)
  • stdout/stderr capture with optional file output (outputPath)
  • Allowlist validation for hook commands (absolute paths only)
  • Comprehensive test coverage (unit + integration) for cross-platform execution
  • Documentation updated in docs/automation/heartbeat.md and docs/automation/cron-jobs.md
  • Backward compatibility: existing configs without preHook continue to work

References

  • PR #17529: add preCheck gate to skip jobs when nothing changed

extent analysis

Fix Plan

To implement the pre-hook mechanism for heartbeat agent triggers, follow these steps:

  1. Update config schema: Add the preHook field to the heartbeat trigger config schema.
  2. Implement pre-hook execution: Run the pre-hook command before agent activation and token consumption.
  3. Handle exit codes: Map exit codes to trigger behavior using the onExit field.
  4. Enforce timeout: Implement timeout enforcement with a configurable duration (default 30s).
  5. Capture stdout/stderr: Capture stdout and stderr output with optional file output (outputPath).
  6. Validate allowlist: Validate hook commands against a configured allowlist (absolute paths only).

Example code ( Node.js ):

const childProcess = require('child_process');
const fs = require('fs');

// Load config
const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));

// Define pre-hook function
function runPreHook(config) {
  const { command, timeout, onExit, capture } = config.preHook;
  const child = childProcess.exec(command, { timeout: timeout * 1000 });

  return new Promise((resolve, reject) => {
    child.on('exit', (code) => {
      const behavior = onExit[code] || (code === 0 ? 'continue' : 'skip');
      resolve({ behavior, output: child.stdout });
    });

    child.on('error', (err) => {
      reject(err);
    });

    if (capture.stdout) {
      const outputPath = capture.outputPath;
      if (outputPath) {
        const writeStream = fs.createWriteStream(outputPath);
        child.stdout.pipe(writeStream);
      }
    }
  });
}

// Run pre-hook
runPreHook(config).then((result) => {
  if (result.behavior === 'continue') {
    // Activate agent
  } else if (result.behavior === 'skip') {
    // Skip agent activation
  } else {
    // Mark trigger as failed
  }
}).catch((err) => {
  // Handle error
});

Verification

To verify the fix, test the pre-hook mechanism with different exit codes, timeouts, and capture configurations. Ensure that the agent activation is skipped or failed accordingly.

Extra Tips

  • Use absolute paths for hook commands to prevent arbitrary command injection.
  • Configure the allowlist to include only trusted executables.
  • Test the pre-hook mechanism thoroughly to ensure correct behavior in different scenarios.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Feature: pre-run script hook/gate for heartbeat and cron agent triggers [1 participants]