Skip to content

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

Closed
hclsys wants to merge 2 commits into
openclaw:mainfrom
hclsys:fix/hooks-wake-respect-agent-session
Closed

fix(hooks): respect agentId and sessionKey for wake-mode hook mappings#64567
hclsys wants to merge 2 commits into
openclaw:mainfrom
hclsys:fix/hooks-wake-respect-agent-session

Conversation

@hclsys

@hclsys hclsys commented Apr 11, 2026

Copy link
Copy Markdown

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

Config Resolved session key
Both sessionKey and agentId Uses sessionKey (explicit)
Only agentId: "integrations" Derives agent:integrations:main
Neither Falls 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

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.

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

This is silent: 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 — events appear in
a completely different agent's session from the configured target.

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

Fix:
- Add agentId/sessionKey to the wake variant of HookAction type
  (hooks-mapping.ts)
- Thread them through buildActionFromMapping + mergeAction
  (hooks-mapping.ts)
- Update dispatchWakeHook to accept + use them, falling back to
  resolveMainSessionKeyFromConfig when neither is provided
  (server/hooks.ts)
- Update the HooksHandlerDeps type to match (server-http.ts)
- Pass the fields through at the mapping dispatch call site
  (server-http.ts:631)

When agentId is provided without sessionKey, the session key is
derived as `agent:${agentId}:main` — the agent's default main
session, matching the convention used elsewhere.

Fixes openclaw#64556
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: XS labels Apr 11, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90ed1c8a36

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 636 to +640
dispatchWakeHook({
text: mapped.action.text,
mode: mapped.action.mode,
agentId: mapped.action.agentId,
sessionKey: mapped.action.sessionKey,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce session-key policy before mapped wake dispatch

This mapped-wake path now forwards mapped.action.sessionKey directly to dispatchWakeHook without the resolveHookSessionKey/isSessionKeyAllowedByPrefix checks used by mapped agent actions. In mappings that template or transform sessionKey from webhook payload data, callers can now inject wake events into arbitrary sessions even when hooks.allowedSessionKeyPrefixes is configured, which is a policy bypass introduced by this routing change.

Useful? React with 👍 / 👎.

Comment on lines +43 to +45
(value.agentId
? `agent:${value.agentId}:main`
: resolveMainSessionKeyFromConfig());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve wake agent IDs through hook agent policy

The wake dispatcher derives agent:<agentId>:main from raw agentId and never applies resolveHookTargetAgentId/isHookAgentAllowed, so mapped wake hooks can target disallowed agents and no longer get unknown-agent fallback behavior. Any mapping or transform that sets agentId now bypasses the same agent policy gates enforced for mapped agent actions, causing inconsistent and potentially unauthorized routing.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR correctly wires agentId and sessionKey from wake-mode hook mapping configs through to dispatchWakeHook, fixing silent misrouting in multi-agent setups. The type changes in hooks-mapping.ts and dispatch plumbing in server-http.ts are clean.

  • The session key derived from agentId in server/hooks.ts uses the raw (trimmed-only) string rather than calling normalizeAgentId first. Every other session key construction path (buildMainSessionKey, normalizeHookDispatchSessionKey) lowercases via normalizeAgentId, and the agent-action mapping path routes through resolveHookTargetAgentId which handles both normalization and unknown-agent fallback. A mixed-case or unrecognized agentId in a wake mapping will silently produce a session key that matches nothing, returning 200 {"ok":true} with no delivery — the same failure mode this PR aims to eliminate.

Confidence Score: 4/5

Safe to merge after addressing the agentId normalization gap in dispatchWakeHook.

The core fix (threading agentId/sessionKey through the wake mapping path) is structurally correct and the plumbing in hooks-mapping.ts and server-http.ts looks good. One P1 defect remains: the session key derived from agentId skips normalizeAgentId, creating a silent misroute for mixed-case or unknown agent IDs — the same class of failure the PR is meant to close.

src/gateway/server/hooks.ts — the agentId-to-session-key derivation needs normalizeAgentId applied before embedding the ID in the key string.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/server/hooks.ts
Line: 41-46

Comment:
**`agentId` not normalized before building session key**

The derived session key uses the raw (trimmed-only) agent ID. `buildMainSessionKey` and `normalizeHookDispatchSessionKey` both call `normalizeAgentId` which lowercases the ID before embedding it in the key. If a wake mapping has a mixed-case `agentId`, the resulting session key won't match the real session — silently routing the event nowhere, with a `200 ok` response.

The agent-action mapping path avoids this by going through `resolveHookTargetAgentId` (which normalizes and also falls back to the config default agent for unknown IDs). The wake path bypasses both normalization and that fallback. The fix is to call `normalizeAgentId` on `value.agentId` before embedding it in the session key string, consistent with how `buildMainSessionKey` in `main-session.ts` does it.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(hooks): respect agentId and sessionK..." | Re-trigger Greptile

Comment on lines +41 to 46
const sessionKey =
value.sessionKey ??
(value.agentId
? `agent:${value.agentId}:main`
: resolveMainSessionKeyFromConfig());
enqueueSystemEvent(value.text, { sessionKey, trusted: false });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 agentId not normalized before building session key

The derived session key uses the raw (trimmed-only) agent ID. buildMainSessionKey and normalizeHookDispatchSessionKey both call normalizeAgentId which lowercases the ID before embedding it in the key. If a wake mapping has a mixed-case agentId, the resulting session key won't match the real session — silently routing the event nowhere, with a 200 ok response.

The agent-action mapping path avoids this by going through resolveHookTargetAgentId (which normalizes and also falls back to the config default agent for unknown IDs). The wake path bypasses both normalization and that fallback. The fix is to call normalizeAgentId on value.agentId before embedding it in the session key string, consistent with how buildMainSessionKey in main-session.ts does it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/server/hooks.ts
Line: 41-46

Comment:
**`agentId` not normalized before building session key**

The derived session key uses the raw (trimmed-only) agent ID. `buildMainSessionKey` and `normalizeHookDispatchSessionKey` both call `normalizeAgentId` which lowercases the ID before embedding it in the key. If a wake mapping has a mixed-case `agentId`, the resulting session key won't match the real session — silently routing the event nowhere, with a `200 ok` response.

The agent-action mapping path avoids this by going through `resolveHookTargetAgentId` (which normalizes and also falls back to the config default agent for unknown IDs). The wake path bypasses both normalization and that fallback. The fix is to call `normalizeAgentId` on `value.agentId` before embedding it in the session key string, consistent with how `buildMainSessionKey` in `main-session.ts` does it.

How can I resolve this? If you propose a fix, please make it concise.

The agentId-to-session-key derivation used the raw value without
normalizeAgentId(), so mixed-case agent IDs produced keys that didn't
match any real session — silently routing the event nowhere while
returning 200 ok. The agent-action path avoids this by going through
resolveHookTargetAgentId (which normalizes). Apply the same
normalization to the wake path for consistency.

Addresses greptile 4/5 P1 feedback on openclaw#64567.
@hclsys

This comment was marked as low quality.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4185d84d02

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 636 to +640
dispatchWakeHook({
text: mapped.action.text,
mode: mapped.action.mode,
agentId: mapped.action.agentId,
sessionKey: mapped.action.sessionKey,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce hook routing policy for mapped wake actions

This wake-mapping branch dispatches immediately with agentId/sessionKey and returns before running the policy checks used for mapped agent actions (isHookAgentAllowed, resolveHookSessionKey, and prefix enforcement). As a result, a hooks.mappings wake rule (especially one using transform/payload-derived routing fields) can route to disallowed agents or session-key prefixes even when hooks.allowedAgentIds / hooks.allowedSessionKeyPrefixes are configured, which breaks the documented hook safety constraints and can silently route events into unauthorized sessions.

Useful? React with 👍 / 👎.

@hclsys

This comment was marked as low quality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: hooks.mappings[].agentId and sessionKey silently ignored for action="wake"

1 participant