openclaw - ✅(Solved) Fix [Bug]: hooks.mappings[].agentId and sessionKey silently ignored for action="wake" [1 pull requests, 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#64556Fetched 2026-04-11 06:14:24
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1referenced ×1

For webhook mappings configured with action: "wake", the agentId and sessionKey fields in hooks.mappings[*] are accepted by the config schema but silently ignored at dispatch time. Every wake-mode hook ends up enqueued into the default main agent's heartbeat session, regardless of the configured target.

This is silent — the gateway accepts the request, returns 200 {"ok":true,"mode":"now"}, and the user has no indication that the routing was disregarded.

Root Cause

dispatchWakeHook in the gateway hooks request handler hard-codes the target sessionKey to whatever resolveMainSessionKeyFromConfig() returns, ignoring value.sessionKey (and by extension agentId):

// dist/server.impl-*.js, ~line 26042 in v2026.4.9
const dispatchWakeHook = (value) => {
    const sessionKey = resolveMainSessionKeyFromConfig();   // ← always main
    enqueueSystemEvent(value.text, {
        sessionKey,
        trusted: false
    });
    if (value.mode === "now") requestHeartbeatNow({ reason: "hook:wake" });
};

For comparison, dispatchAgentHook (used when action: "agent") does respect value.sessionKey and the configured agentId because it builds an isolated cron job with explicit delivery — see ~line 26050 in the same file.

Fix Action

Fix / Workaround

For webhook mappings configured with action: "wake", the agentId and sessionKey fields in hooks.mappings[*] are accepted by the config schema but silently ignored at dispatch time. Every wake-mode hook ends up enqueued into the default main agent's heartbeat session, regardless of the configured target.

dispatchWakeHook in the gateway hooks request handler hard-codes the target sessionKey to whatever resolveMainSessionKeyFromConfig() returns, ignoring value.sessionKey (and by extension agentId):

// dist/server.impl-*.js, ~line 26042 in v2026.4.9
const dispatchWakeHook = (value) => {
    const sessionKey = resolveMainSessionKeyFromConfig();   // ← always main
    enqueueSystemEvent(value.text, {
        sessionKey,
        trusted: false
    });
    if (value.mode === "now") requestHeartbeatNow({ reason: "hook:wake" });
};

PR fix notes

PR #64567: fix(hooks): respect agentId and sessionKey for wake-mode hook mappings

Description (problem / solution / changelog)

Summary

dispatchWakeHook() hard-codes the session target to resolveMainSessionKeyFromConfig(), ignoring any agentId or sessionKey configured in hooks.mappings[]. Every wake-mode hook ends up in the default main agent's heartbeat session regardless of the mapping config — silently.

The gateway returns 200 {"ok":true,"mode":"now"} and the user has no indication that routing was disregarded. Multi-agent setups silently funnel all wake hooks into the wrong agent.

Fixes #64556

Root cause

// server/hooks.ts (before fix)
const dispatchWakeHook = (value: { text: string; mode: "now" | "next-heartbeat" }) => {
  const sessionKey = resolveMainSessionKeyFromConfig();  // ← always main
  enqueueSystemEvent(value.text, { sessionKey, trusted: false });
};

For comparison, dispatchAgentHook (the action: "agent" path) already resolves the hook's sessionKey and agentId from the mapping config via resolveHookSessionKey + resolveHookTargetAgentId. The wake path was simply never wired.

The mapping dispatch call at server-http.ts:631 passed only text and mode, discarding the agentId/sessionKey that buildActionFromMapping could have propagated from the hook mapping config.

Fix

Three files, ~30 LOC:

hooks-mapping.ts:

  • Add agentId?: string and sessionKey?: string to the wake variant of HookAction
  • Thread them through buildActionFromMapping (from mapping.agentId / mapping.sessionKey)
  • Thread them through mergeAction (for transform override support)

server/hooks.ts:

  • Expand dispatchWakeHook signature to accept optional agentId / sessionKey
  • Resolve session key with fallback chain: value.sessionKey ?? agent:${value.agentId}:main ?? resolveMainSessionKeyFromConfig()

server-http.ts:

  • Update HooksHandlerDeps.dispatchWakeHook type to match
  • Pass mapped.action.agentId / mapped.action.sessionKey at the mapping dispatch site

Fallback behavior

ConfigResolved session key
Both sessionKey and agentIdUses sessionKey (explicit)
Only agentId: "integrations"Derives agent:integrations:main
NeitherFalls back to resolveMainSessionKeyFromConfig() (existing behavior)

What about the direct /hooks/wake path?

The direct POST /hooks/wake path at server-http.ts:533-541 sends normalizeWakePayload(payload) which only extracts text and mode from the request body. This path continues to use the main session key — which is correct because direct wake requests don't have a mapping config to draw routing from. Only mapped wake hooks (hooks.mappings[].action: "wake") gain the new routing.

Related work

  • PR #39046 (open, 33 days, 0 reviews): fix(hooks): gateway hook event routing respects target agent session — fixes hook completion/error system event routing (different function, different code path). Complementary, not conflicting: #39046 fixes buildCompletionSystemEvent, this PR fixes dispatchWakeHook. Both touch server/hooks.ts but different functions.
  • #3432 (closed): original feature request for this routing. Fields were later added to the schema, but implementation was never wired through for wake.

Scope

  • Files: hooks-mapping.ts (+10/-1), server/hooks.ts (+14/-2), server-http.ts (+7/-1)
  • Production LOC: ~30 lines across 3 files
  • oxlint clean
  • No changes to the agent action path, no changes to resolveHookSessionKey, no changes to the direct /hooks/wake endpoint

cc @steipete — gateway hooks routing. Credit to @jaserNo1 for the complete RCA and clear reproduction in #64556.

Changed files

  • src/gateway/hooks-mapping.ts (modified, +11/-1)
  • src/gateway/server-http.ts (modified, +8/-1)
  • src/gateway/server/hooks.ts (modified, +19/-2)

Code Example

{
  hooks: {
    enabled: true,
    path: "/hooks",
    token: "<token>",
    mappings: [
      {
        id: "example-hook",
        match: { path: "example" },
        action: "wake",
        wakeMode: "now",
        agentId: "integrations",     // ← intended target
        sessionKey: "hook:example",  // ← intended session
        textTemplate: "Hello from {{name}}"
      }
    ]
  }
}

---

curl -X POST http://127.0.0.1:18789/hooks/example \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"name":"world"}'
# → HTTP 200 {"ok":true,"mode":"now"}

---

// dist/server.impl-*.js, ~line 26042 in v2026.4.9
const dispatchWakeHook = (value) => {
    const sessionKey = resolveMainSessionKeyFromConfig();   // ← always main
    enqueueSystemEvent(value.text, {
        sessionKey,
        trusted: false
    });
    if (value.mode === "now") requestHeartbeatNow({ reason: "hook:wake" });
};

---

const dispatchWakeHook = (value) => {
    const sessionKey = value.sessionKey
        ?? resolveSessionKeyForAgent(value.agentId)
        ?? resolveMainSessionKeyFromConfig();
    enqueueSystemEvent(value.text, { sessionKey, trusted: false });
    if (value.mode === "now") requestHeartbeatNow({ reason: `hook:wake:${sessionKey}` });
};
RAW_BUFFERClick to expand / collapse

Summary

For webhook mappings configured with action: "wake", the agentId and sessionKey fields in hooks.mappings[*] are accepted by the config schema but silently ignored at dispatch time. Every wake-mode hook ends up enqueued into the default main agent's heartbeat session, regardless of the configured target.

This is silent — the gateway accepts the request, returns 200 {"ok":true,"mode":"now"}, and the user has no indication that the routing was disregarded.

Reproduction

  1. Configure a wake hook in openclaw.json targeting a non-default agent:
{
  hooks: {
    enabled: true,
    path: "/hooks",
    token: "<token>",
    mappings: [
      {
        id: "example-hook",
        match: { path: "example" },
        action: "wake",
        wakeMode: "now",
        agentId: "integrations",     // ← intended target
        sessionKey: "hook:example",  // ← intended session
        textTemplate: "Hello from {{name}}"
      }
    ]
  }
}
  1. Fire a request:
curl -X POST http://127.0.0.1:18789/hooks/example \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"name":"world"}'
# → HTTP 200 {"ok":true,"mode":"now"}
  1. Inspect where the event landed:
    • Expected: agent:integrations:hook:example queue, integrations agent woken.
    • Actual: the text appears as a System (untrusted): […] line in the agent:main:main heartbeat session of the main agent. The integrations agent never wakes; its hook:example session never receives anything.

Root cause

dispatchWakeHook in the gateway hooks request handler hard-codes the target sessionKey to whatever resolveMainSessionKeyFromConfig() returns, ignoring value.sessionKey (and by extension agentId):

// dist/server.impl-*.js, ~line 26042 in v2026.4.9
const dispatchWakeHook = (value) => {
    const sessionKey = resolveMainSessionKeyFromConfig();   // ← always main
    enqueueSystemEvent(value.text, {
        sessionKey,
        trusted: false
    });
    if (value.mode === "now") requestHeartbeatNow({ reason: "hook:wake" });
};

For comparison, dispatchAgentHook (used when action: "agent") does respect value.sessionKey and the configured agentId because it builds an isolated cron job with explicit delivery — see ~line 26050 in the same file.

Why this matters

  • The schema for hooks.mappings[*] accepts agentId and sessionKey (per the type definitions in plugin-sdk/src/config/types.gateway.d.ts), so users reasonably expect them to take effect for any action.
  • Multi-agent setups silently funnel all wake hooks into the main agent. Users debugging "why doesn't my dedicated agent see the events" find a 200 OK at the gateway, no log entries, and events that appear in a completely different agent's session — making this very hard to diagnose.
  • Related history: #3432 (feature request for exactly this routing) was closed without action; the fields were later added to the schema, but the implementation was never wired through for wake.

Suggested fix

Either:

(a) Honor the configured sessionKey (and agentId, via session-key resolution) in dispatchWakeHook, falling back to the main session key only when none is provided:

const dispatchWakeHook = (value) => {
    const sessionKey = value.sessionKey
        ?? resolveSessionKeyForAgent(value.agentId)
        ?? resolveMainSessionKeyFromConfig();
    enqueueSystemEvent(value.text, { sessionKey, trusted: false });
    if (value.mode === "now") requestHeartbeatNow({ reason: `hook:wake:${sessionKey}` });
};

(b) If routing wake hooks to non-default agents is not intended, remove agentId/sessionKey from the schema for action: "wake" mappings and surface a config-time warning when they're set, so users aren't misled.

Either path is better than the current state where the config and runtime disagree silently.

Environment

  • openclaw 2026.4.9
  • Reproduced on macOS, but the bug is in dist/server.impl-*.js and is platform-independent.

extent analysis

TL;DR

To fix the issue where agentId and sessionKey fields are silently ignored for action: "wake" webhook mappings, update the dispatchWakeHook function to honor the configured sessionKey and agentId.

Guidance

  • Identify the dispatchWakeHook function in the gateway hooks request handler and update it to use the provided sessionKey and agentId instead of hard-coding the target session key.
  • Consider adding a config-time warning when agentId and sessionKey are set for action: "wake" mappings if routing wake hooks to non-default agents is not intended.
  • Review the resolveSessionKeyForAgent function to ensure it correctly resolves the session key for the provided agentId.
  • Test the updated dispatchWakeHook function with different agentId and sessionKey configurations to verify that the events are correctly routed to the intended agent and session.

Example

const dispatchWakeHook = (value) => {
    const sessionKey = value.sessionKey
        ?? resolveSessionKeyForAgent(value.agentId)
        ?? resolveMainSessionKeyFromConfig();
    enqueueSystemEvent(value.text, { sessionKey, trusted: false });
    if (value.mode === "now") requestHeartbeatNow({ reason: `hook:wake:${sessionKey}` });
};

Notes

The provided fix assumes that the resolveSessionKeyForAgent function is correctly implemented and can resolve the session key for the provided agentId. If this function is not implemented or is incorrect, additional changes may be required.

Recommendation

Apply workaround (a) by updating the dispatchWakeHook function to honor the configured sessionKey and agentId, as this will allow for correct routing of wake hooks to non-default agents. This approach is preferred over removing agentId and sessionKey from the schema, as it provides more flexibility and functionality for users.

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 - ✅(Solved) Fix [Bug]: hooks.mappings[].agentId and sessionKey silently ignored for action="wake" [1 pull requests, 1 participants]