openclaw - 💡(How to fix) Fix [Feature]: Add `before_agent_run` hook support to embedded runner

Official PRs (…)
ON THIS PAGE

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…

Request adding before_agent_run gate hook support to the embedded runner (used by Feishu, Slack, webhook external channels). Currently this hook is only available in the selection runner (Chat Web UI) and CLI runner, creating an inconsistency where security/compliance plugins cannot perform input gating on external channel messages.

Root Cause

Request adding before_agent_run gate hook support to the embedded runner (used by Feishu, Slack, webhook external channels). Currently this hook is only available in the selection runner (Chat Web UI) and CLI runner, creating an inconsistency where security/compliance plugins cannot perform input gating on external channel messages.

Fix Action

Fix / Workaround

HookEmbedded RunnerSelection RunnerCLI Runner
message_received✅ (dispatch layer)
before_agent_reply✅ (cron only)
before_model_resolve
before_prompt_build
before_agent_run

Affected: External channel users (Feishu, Slack, webhooks) and security plugins depending on before_agent_run. Severity: Critical – security/compliance input gate bypassed for all external traffic; fail-closed semantics broken. Frequenc: Always – every message from external channels triggers the missing hook. Consequence: Inconsistent plugin behavior, potential data leakage, compliance gaps, and mandatory manual workarounds.

Code Example

Hooks called:
- before_model_resolve    (line ~1415)
- before_agent_start      (legacy, line ~1424)
- before_compaction       (line ~175)
- after_compaction        (line ~246)
- before_agent_reply      (cron trigger only, line ~1760)

---

// Line ~14254
if (hookRunner?.hasHooks("before_agent_run")) {
    beforeRunResult = await hookRunner.runBeforeAgentRun({
        prompt: params.prompt,
        messages: params.messages,
        systemPrompt: params.systemPrompt,
        // ...
    }, hookContext);
}

---

// Line ~163, ~407
const hasBeforeAgentRunHooks = hookRunner?.hasHooks("before_agent_run") === true;
beforeRunResult = await hookRunner.runBeforeAgentRun({ ... }, hookContext);

---

// In agent-harness-runtime-BMrsaaM3.js
// After prompt build, before model call:

if (hookRunner?.hasHooks("before_agent_run")) {
    const gateEvent = {
        prompt: finalPrompt,
        messages: sessionMessages,
        systemPrompt: finalSystemPrompt,
        // accountId, channelId, senderId, senderIsOwner
        // — populated from hook context where available
    };

    const gateResult = await hookRunner.runBeforeAgentRun(gateEvent, hookContext);

    if (gateResult?.decision?.outcome === "block") {
        // Fail-closed: propagate block with plugin attribution
        throw new BlockedByBeforeAgentRunError({
            blockedBy: gateResult.pluginId ?? "before_agent_run",
            reason: gateResult.decision.reason ?? "Blocked by before_agent_run",
            message: gateResult.decision.message,
            category: gateResult.decision.category,
        });
    }
}
RAW_BUFFERClick to expand / collapse

Summary

Request adding before_agent_run gate hook support to the embedded runner (used by Feishu, Slack, webhook external channels). Currently this hook is only available in the selection runner (Chat Web UI) and CLI runner, creating an inconsistency where security/compliance plugins cannot perform input gating on external channel messages.

Problem to solve

before_agent_run is described as the "input gate" — the final checkpoint after prompt / system prompt / history are fully assembled, before model token consumption begins.

Security plugins rely on this hook to:

  • Inspect the complete context (prompt + messages history + system prompt)
  • Apply content moderation, PII detection, quota checks
  • Enforce fail-closed blocking with consistent semantics

Without this hook in the embedded runner path, external channel traffic (Feishu, Slack, etc.) bypasses the input gate entirely, making security plugin behavior inconsistent across interaction channels.

Current State

Hook availability by runner

HookEmbedded RunnerSelection RunnerCLI Runner
message_received✅ (dispatch layer)
before_agent_reply✅ (cron only)
before_model_resolve
before_prompt_build
before_agent_run

Source code analysis

Embedded runner (pi-embedded-ydXicp1O.js) — no before_agent_run references:

Hooks called:
- before_model_resolve   ✅ (line ~1415)
- before_agent_start     ✅ (legacy, line ~1424)
- before_compaction      ✅ (line ~175)
- after_compaction       ✅ (line ~246)
- before_agent_reply     ✅ (cron trigger only, line ~1760)

Selection runner (selection-Cr-9-UpD.js) — fully implemented:

// Line ~14254
if (hookRunner?.hasHooks("before_agent_run")) {
    beforeRunResult = await hookRunner.runBeforeAgentRun({
        prompt: params.prompt,
        messages: params.messages,
        systemPrompt: params.systemPrompt,
        // ...
    }, hookContext);
}

CLI runner (cli-runner-jPUQ9KeD.js) — fully implemented:

// Line ~163, ~407
const hasBeforeAgentRunHooks = hookRunner?.hasHooks("before_agent_run") === true;
beforeRunResult = await hookRunner.runBeforeAgentRun({ ... }, hookContext);

Proposed solution

Add before_agent_run invocation to the embedded runner path, ideally in the agent harness runtime (agent-harness-runtime-BMrsaaM3.js) where before_prompt_build is already resolved. This ensures all runner paths share the same input gate semantics.

Suggested insertion point — after before_prompt_build resolves the final prompt, before model inference begins:

// In agent-harness-runtime-BMrsaaM3.js
// After prompt build, before model call:

if (hookRunner?.hasHooks("before_agent_run")) {
    const gateEvent = {
        prompt: finalPrompt,
        messages: sessionMessages,
        systemPrompt: finalSystemPrompt,
        // accountId, channelId, senderId, senderIsOwner
        // — populated from hook context where available
    };

    const gateResult = await hookRunner.runBeforeAgentRun(gateEvent, hookContext);

    if (gateResult?.decision?.outcome === "block") {
        // Fail-closed: propagate block with plugin attribution
        throw new BlockedByBeforeAgentRunError({
            blockedBy: gateResult.pluginId ?? "before_agent_run",
            reason: gateResult.decision.reason ?? "Blocked by before_agent_run",
            message: gateResult.decision.message,
            category: gateResult.decision.category,
        });
    }
}

Alternatives considered

No response

Impact

Affected: External channel users (Feishu, Slack, webhooks) and security plugins depending on before_agent_run. Severity: Critical – security/compliance input gate bypassed for all external traffic; fail-closed semantics broken. Frequenc: Always – every message from external channels triggers the missing hook. Consequence: Inconsistent plugin behavior, potential data leakage, compliance gaps, and mandatory manual workarounds.

Evidence/examples

No response

Additional information

No response

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]: Add `before_agent_run` hook support to embedded runner