Skip to content

fix: route wake hooks to explicit sessions#70268

Open
howardclaw26 wants to merge 5 commits into
openclaw:mainfrom
howardclaw26:howard/session-keyed-wake-hooks
Open

fix: route wake hooks to explicit sessions#70268
howardclaw26 wants to merge 5 commits into
openclaw:mainfrom
howardclaw26:howard/session-keyed-wake-hooks

Conversation

@howardclaw26

@howardclaw26 howardclaw26 commented Apr 22, 2026

Copy link
Copy Markdown

Summary

  • let direct /hooks/wake payloads carry an optional sessionKey
  • honor action: "wake" mapping sessionKey / agentId routing instead of always waking main
  • extend wake-hook tests and config guards so templated wake session keys follow the same policy as agent hooks

Why

The current gateway silently ignores wake-hook session targeting and always enqueues into the default main session, even when the operator provided a wake session key or configured a wake mapping with agent/session routing.

This is what bit the GitHub webhook relay dogfood path: the relay could wake Howard, but not the intended existing session, so the signal was quieter than intended and could drift into the wrong place.

Fixes #64556.

Notes

  • mapped wake hooks with agentId and no explicit sessionKey now target that agent's main session instead of falling back to the default main agent session
  • direct /hooks/wake treats sessionKey as an exact explicit target and does not do agent rebinding; mapped wake keeps the existing target-agent normalization path

Validation

  • corepack pnpm exec vitest run --config test/vitest/vitest.gateway.config.ts src/gateway/hooks.test.ts src/gateway/hooks-mapping.test.ts src/gateway/server.hooks.test.ts
  • pre-commit check:changed gate (including typecheck, lint, import-cycle checks, and the gateway test shard)
  • Cursor Composer (composer-2-fast) second-mind review on the final diff: ship, with the config-guard gap fixed before opening

@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR routes wake-hook signals to explicit session targets instead of always enqueuing into the default main session. It extends HookAction and normalizeWakePayload to carry sessionKey, adds agentId/sessionKey/sessionKeySource to the \"wake\" action type, fixes normalizeHookDispatchSessionKey to scope non-agent-prefixed keys to the target agent (previously they were returned unmodified), and closes the config-guard gap so templated sessionKey on wake mappings now requires allowedSessionKeyPrefixes just like agent mappings. The integration test in server.hooks.test.ts covers both the direct-wake canonical-key path and the mapped-agent-wake path end-to-end.

Confidence Score: 4/5

Safe to merge with one minor policy-enforcement gap addressed.

All core routing logic is correct and well-tested. The one finding is the missing allowedSessionKeyPrefixes guard in the else if (targetAgentId) branch of the mapped-wake handler — the auto-computed agent:${targetAgentId}:main key bypasses the prefix policy that every other code path enforces. While not exploitable by external callers (the key is fully operator-controlled), it creates a silent policy bypass for operators who combine strict prefix constraints with agentId-only wake mappings.

src/gateway/server-http.ts — the else if (targetAgentId) branch in the mapped-wake handler (around line 797).

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

Comment:
**Missing `allowedSessionKeyPrefixes` guard for auto-computed agent main session key**

When a mapped wake action has `agentId` but no explicit `sessionKey`, the code computes `agent:${targetAgentId}:main` and dispatches without checking it against `hooksConfig.sessionPolicy.allowedSessionKeyPrefixes`. The `if (mapped.action.sessionKey)` branch just above (lines 769–796) performs this guard after normalization, as does the agent hook dispatch path. An operator who configures a strict prefix policy expecting all wake targets to be constrained will have that constraint silently bypassed for `agentId`-only wake mappings.

The fix is to mirror the same prefix check used in the `sessionKey`-present branch: after computing `normalizedWakeSessionKey`, read `hooksConfig.sessionPolicy.allowedSessionKeyPrefixes` and call `isSessionKeyAllowedByPrefix` / `getHookSessionKeyPrefixError` before calling `dispatchWakeHook`.

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

Reviews (2): Last reviewed commit: "fix: canonicalize wake hook session targ..." | Re-trigger Greptile

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

Copy link
Copy Markdown

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: 0e3a32445e

ℹ️ 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".

enqueueSystemEvent(value.text, { sessionKey, trusted: false });
if (value.mode === "now") {
requestHeartbeatNow({ reason: "hook:wake" });
requestHeartbeatNow({ reason: "hook:wake", sessionKey });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep wake target aligned with queued session key

Do not pass arbitrary wake sessionKey values directly to requestHeartbeatNow here, because the event is queued under the raw key while heartbeat targeting canonicalizes non-agent: keys (e.g. hook:*) to an agent-scoped key in resolveHeartbeatSession; that means the wake run checks a different session than the one that received the event, so explicit /hooks/wake and mapped wake sessions with non-agent: keys can be left unprocessed (and can be skipped entirely when no main agent exists).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 091eec222d. I now canonicalize the wake hook session target before both queueing the system event and requesting the heartbeat, and added regressions for direct /hooks/wake plus mapped wake routes with non-agent session keys.

@howardclaw26 howardclaw26 marked this pull request as draft April 22, 2026 17:02
@howardclaw26

howardclaw26 commented Apr 22, 2026

Copy link
Copy Markdown
Author

Local proof / test notes:

  • Real end-to-end wake proof: I forced a deterministic failing CI run on a throwaway private-repo PR and verified the resulting GitHub wake landed in this exact Signal session, where the session still contained the marker wakeproof-9w31ZsYvKf2rN8Qm.
  • Review feedback bug fixed in 091eec222d: wake hooks now canonicalize the target session key before both queueing the system event and requesting the heartbeat, so non-agent keys do not wake the wrong session.
  • Added regressions for both direct /hooks/wake and mapped wake routes with non-agent session keys.
  • Validation: targeted gateway tests passed, then check:changed passed cleanly across the changed gateway surface.

This is the proof bar I wanted before treating explicit wake-session routing as real rather than just code-plausible.

@howardclaw26 howardclaw26 marked this pull request as ready for review April 22, 2026 17:23

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

Copy link
Copy Markdown

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: 091eec222d

ℹ️ 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".

enqueueSystemEvent(value.text, { sessionKey, trusted: false });
if (value.mode === "now") {
requestHeartbeatNow({ reason: "hook:wake" });
requestHeartbeatNow({ reason: "hook:wake", sessionKey });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid scoping wake heartbeat to non-agent session keys

When /hooks/wake now accepts an explicit sessionKey, dispatchWakeHook always forwards that key to requestHeartbeatNow; for non-agent canonical keys (for example global in non-global session scope), the heartbeat runner reinterprets the forced target in resolveHeartbeatSession and ends up polling a different queue than the one enqueueSystemEvent wrote to, so mode: "now" can return 200 while the wake event stays unprocessed until a later unrelated run. This is newly reachable because wake hooks can now carry explicit session keys.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Contributor

Maintainer pass: this is close, but I would not land until the prefix-policy gap is fixed.

In src/gateway/server-http.ts, mapped wake with explicit sessionKey validates hooksConfig.sessionPolicy.allowedSessionKeyPrefixes after canonicalization. The else if (targetAgentId) path computes agent:${targetAgentId}:main and dispatches without the same guard. That means an operator who configured strict wake prefixes can bypass them by using an agentId-only wake mapping.

Concrete fix: after computing normalizedWakeSessionKey in the targetAgentId branch, run the same isSessionKeyAllowedByPrefix / getHookSessionKeyPrefixError check used in the explicit-session branch. Add a server.hooks.test.ts case where allowedSessionKeyPrefixes excludes agent:hooks: and an agentId: "hooks" wake mapping is rejected.

@prtags

prtags Bot commented Apr 23, 2026

Copy link
Copy Markdown

Related work from PRtags group obliging-seahorse-7nmd

Title: Open PR duplicate: wake hooks ignore explicit session targets

Number Title
#35231 feat: add --session-key support to system wake/event
#70268* fix: route wake hooks to explicit sessions

* This PR

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

Copy link
Copy Markdown

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: faa83f3dfe

ℹ️ 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 thread src/gateway/hooks.ts
Comment on lines +412 to +415
return toAgentStoreSessionKey({
agentId: targetAgentId,
requestKey: trimmed,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve global key when rebinding hook session targets

normalizeHookDispatchSessionKey now rewrites every non-agent: key through toAgentStoreSessionKey, which converts global into agent:<target>:global. In the new mapped wake flow (src/gateway/server-http.ts lines 797-801), this means a wake mapping with agentId and no explicit sessionKey in session.scope: "global" enqueues to agent:<agent>:global, but the heartbeat runner’s global-scope path only processes global, so the wake event can remain unprocessed indefinitely. Keeping global (and unknown) un-rebound avoids this stuck-queue behavior.

Useful? React with 👍 / 👎.

@howardclaw26 howardclaw26 force-pushed the howard/session-keyed-wake-hooks branch from faa83f3 to 5a9390a Compare April 23, 2026 16:12

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

Copy link
Copy Markdown

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: 5a9390acba

ℹ️ 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 thread src/gateway/server/hooks.ts Outdated
Comment on lines +41 to +45
const requestedSessionKey = value.sessionKey || resolveMainSessionKeyFromConfig();
const { canonicalKey: sessionKey } = loadSessionEntry(requestedSessionKey);
enqueueSystemEvent(value.text, { sessionKey, trusted: false });
if (value.mode === "now") {
requestHeartbeatNow({ reason: "hook:wake" });
requestHeartbeatNow({ reason: "hook:wake", sessionKey });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Guard global scope before waking explicit sessions

When session.scope is "global", this path canonicalizes explicit wake keys through loadSessionEntry, so values like hook:relay:123 become agent-scoped keys before enqueue/wake. The heartbeat runner’s global-scope branch ignores forced session targets and only drains global, so /hooks/wake with an explicit non-global sessionKey can return 200 in mode: "now" while the queued event remains unprocessed. This is newly reachable because this commit adds request/session routing for wake hooks; explicit wake keys should be rejected or coerced to global under global scope.

Useful? React with 👍 / 👎.

@howardclaw26

Copy link
Copy Markdown
Author

Quick maintainer-facing update on the current head (5a9390a):

  • the prefix-policy gap is addressed in the agentId-only mapped wake path
  • src/gateway/server.hooks.test.ts now covers both sides of that behavior:
    • allowed mapped wake agent routing when the computed session prefix is allowlisted
    • denied mapped wake agent routing when the computed session prefix is disallowed

I re-ran the focused local proof on this head:

  • corepack pnpm test:gateway -- src/gateway/server.hooks.test.ts → 22/22 passing
  • node scripts/check-changed.mjs → clean

If this shape matches what you wanted from the policy fix, I think it is ready to land.

@howardclaw26

Copy link
Copy Markdown
Author

Follow-up for the second Codex global-scope wake feedback: fixed in aba81fc173.

What changed:

  • under session.scope: "global", direct /hooks/wake with an explicit non-global sessionKey now rejects instead of enqueueing to a key the heartbeat runner will not drain
  • mapped wake actions with an explicit non-global sessionKey are rejected under global scope for the same reason
  • mapped wake actions with agentId and no explicit sessionKey now route to global under global scope instead of agent:<id>:main

Added regressions in src/gateway/server.hooks.test.ts for:

  • implicit mapped agent wake routing to global
  • direct explicit non-global wake key rejection
  • mapped explicit non-global wake key rejection

Local verification on the pushed head:

  • corepack pnpm test:gateway -- src/gateway/server.hooks.test.ts -t "global session scope"
  • corepack pnpm test:gateway -- src/gateway/server.hooks.test.ts → 25/25 passing
  • node scripts/check-changed.mjs → passed, including 228 gateway test files / 2613 tests

@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 7, 2026, 1:06 AM ET / 05:06 UTC.

Summary
Review failed before ClawSweeper could summarize the requested change.

PR surface: Source +151, Tests +356. Total +507 across 7 files.

Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path.

Review metrics: none identified.

Merge readiness
Overall: 🌊 off-meta tidepool
Proof: 🌊 off-meta tidepool
Patch quality: 🌊 off-meta tidepool
Result: rating does not apply to this item.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] No close action taken because the review did not complete.

Maintainer options:

  1. Decide the mitigation before merge
    Retry the Codex review after fixing the execution failure.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] Review did not complete, so no work-lane recommendation was made.
Review details

Best possible solution:

Retry the Codex review after fixing the execution failure.

Do we have a high-confidence way to reproduce the issue?

Unclear. The review failed before ClawSweeper could establish a reproduction path.

Is this the best way to solve the issue?

Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction.

AGENTS.md: unclear because the file could not be read completely.

Codex review notes: model gpt-5.5, reasoning high; reviewed against fa614d0907e8.

Label changes

Label changes:

  • add rating: 🌊 off-meta tidepool: Overall readiness is 🌊 off-meta tidepool; proof is 🌊 off-meta tidepool and patch quality is 🌊 off-meta tidepool.
  • remove P2: Current review triage priority is none.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🌊 off-meta tidepool, so this older rating label is no longer current.
  • remove merge-risk: 🚨 compatibility: Current PR review selected no merge-risk labels.
  • remove merge-risk: 🚨 message-delivery: Current PR review selected no merge-risk labels.
  • remove status: 📣 needs proof: Current PR status no longer selects a status label.

Label justifications:

  • rating: 🌊 off-meta tidepool: Overall readiness is 🌊 off-meta tidepool; proof is 🌊 off-meta tidepool and patch quality is 🌊 off-meta tidepool.
Evidence reviewed

PR surface:

Source +151, Tests +356. Total +507 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 4 171 20 +151
Tests 3 358 2 +356
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 7 529 22 +507

What I checked:

  • failure reason: codex execution failed.
  • codex failure detail: Codex review failed for this PR with exit 1.
  • codex stdout: Per-item Codex failure; continuing with the rest of the shard.

Likely related people:

  • unknown: Codex failed before it could trace repository history. (role: review did not complete; confidence: low)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@howardclaw26 howardclaw26 force-pushed the howard/session-keyed-wake-hooks branch from aba81fc to a029bf0 Compare April 28, 2026 03:30
@howardclaw26

Copy link
Copy Markdown
Author

Updated this PR onto current main and adapted it to the split hooks handler layout.

What changed in the rebase/adaptation:

  • moved the request-handler wake-routing logic out of the old src/gateway/server-http.ts path and into src/gateway/server/hooks-request-handler.ts
  • kept dispatcher/canonical-session behavior in src/gateway/server/hooks.ts
  • preserved the maintainer-requested prefix-policy guard for agentId-only mapped wake routes
  • preserved the global-session safeguards so explicit non-global wake session keys are rejected under session.scope = "global", while mapped agentId wake routing drains through global

Local validation on the rebased head a029bf073a1:

  • corepack pnpm test:gateway -- src/gateway/server.hooks.test.ts -t "wake" → 25/25 passing
  • node scripts/check-changed.mjs → passed

The PR diff now touches the current hook modules/tests only: hooks*.ts, server.hooks.test.ts, server/hooks-request-handler.ts, and server/hooks.ts.

@howardclaw26

Copy link
Copy Markdown
Author

Follow-up update: I fixed the residual global-scope prefix-policy footgun noted in second-mind review.

Behavior after this push:

  • session.scope = "global" still rejects explicit wake session keys that cannot be drained globally.
  • Canonical sessionKey: "global" wake routing is allowed even when hooks.allowedSessionKeyPrefixes does not include the literal global prefix.
  • Mapped wake routes with only agentId still drain through global in global scope, without requiring global in the prefix allowlist.
  • Agent hooks and non-global wake session routing still enforce the existing prefix policy.

Validation on new head ceba7d487e:

  • corepack pnpm test:gateway -- src/gateway/server.hooks.test.ts -t "global" → 26/26 passing
  • corepack pnpm test:gateway -- src/gateway/server.hooks.test.ts -t "wake" → 26/26 passing
  • PATH=/tmp/openclaw-bin:$PATH node scripts/check-changed.mjs → passed
  • Cursor composer-2-fast second-mind review → READY

anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
The cron `wake` MCP tool currently forwards only `{mode, text}` to the
gateway. Every wake then enqueues a system event with no sessionKey /
agentId, so the cron service falls back to the heartbeat / main
default. Wakes scheduled from a non-main session (Telegram thread,
Discord channel, multi-agent setup) silently route to the wrong
conversation lane — and on CLI runtimes the woken session burns
tokens generating output that no caller can route back.

Origin capture (closes the upstream half of openclaw#46886 and openclaw#64556):

  - `src/agents/tools/cron-tool.ts` — the `wake` case now resolves
    `opts.agentSessionKey` through `resolveInternalSessionKey` and
    `resolveSessionAgentId`, matching the existing `add` action at
    L569-583. Explicit `sessionKey` / `agentId` params on the tool
    call take precedence over the inferred values so cross-session
    wakes remain expressible.
  - `src/gateway/protocol/schema/agent.ts` — `WakeParamsSchema` now
    declares optional `sessionKey` and `agentId` (NonEmptyString)
    so the gateway-level validator types them explicitly. The
    schema's `additionalProperties: true` continues to accept
    forward-compat metadata unchanged.
  - `src/gateway/server-methods/cron.ts` — the wake handler reads
    both fields, trims them, and forwards to `context.cron.wake`.
  - `src/cron/service.ts` + `src/cron/service/ops.ts` +
    `src/cron/service/timer.ts` — `wake()` accepts optional
    `sessionKey`/`agentId` and threads them into
    `enqueueSystemEvent` and `requestHeartbeatNow`. Missing /
    whitespace-only fields fall through to the dep's default so
    pre-existing call sites with no origin keep behaving the same
    way (backwards compatible).

Tests:

  - `src/cron/service/wake-origin.test.ts` (new, 6 tests) —
    direct seams on `wake()`: origin forwarded on `mode: "now"`,
    queued on `mode: "next-heartbeat"`, default fallback when
    origin omitted, whitespace-only origin treated as omitted,
    empty text still rejected.
  - `src/gateway/protocol/index.test.ts` — extends
    `validateWakeParams` coverage with the new optional fields
    accepted + empty-string rejected.

Out of scope (deliberate split):

  - Channel/thread/topic capture on the job's `delivery` block —
    follow-up PR once this contract lands. The minimum-viable fix
    here is session routing, which unblocks the CLI runtime's
    `--resume <session>` path and the embedded session-resolution
    path without a schema rewrite of the delivery contract.
  - The 5 stalled wake-related PRs (openclaw#70268, openclaw#57199, openclaw#82767,
    openclaw#79869, openclaw#63096) each fix downstream specifics. This PR fixes
    the upstream origin-capture they all silently assume.
anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
The cron `wake` MCP tool currently forwards only `{mode, text}` to the
gateway. Every wake then enqueues a system event with no sessionKey /
agentId, so the cron service falls back to the heartbeat / main
default. Wakes scheduled from a non-main session (Telegram thread,
Discord channel, multi-agent setup) silently route to the wrong
conversation lane — and on CLI runtimes the woken session burns
tokens generating output that no caller can route back.

Origin capture (closes the upstream half of openclaw#46886 and openclaw#64556):

  - `src/agents/tools/cron-tool.ts` — the `wake` case now resolves
    `opts.agentSessionKey` through `resolveInternalSessionKey` and
    `resolveSessionAgentId`, matching the existing `add` action at
    L569-583. Explicit `sessionKey` / `agentId` params on the tool
    call take precedence over the inferred values so cross-session
    wakes remain expressible.
  - `src/gateway/protocol/schema/agent.ts` — `WakeParamsSchema` now
    declares optional `sessionKey` and `agentId` (NonEmptyString)
    so the gateway-level validator types them explicitly. The
    schema's `additionalProperties: true` continues to accept
    forward-compat metadata unchanged.
  - `src/gateway/server-methods/cron.ts` — the wake handler reads
    both fields, trims them, and forwards to `context.cron.wake`.
  - `src/cron/service.ts` + `src/cron/service/ops.ts` +
    `src/cron/service/timer.ts` — `wake()` accepts optional
    `sessionKey`/`agentId` and threads them into
    `enqueueSystemEvent` and `requestHeartbeatNow`. Missing /
    whitespace-only fields fall through to the dep's default so
    pre-existing call sites with no origin keep behaving the same
    way (backwards compatible).

Tests:

  - `src/cron/service/wake-origin.test.ts` (new, 6 tests) —
    direct seams on `wake()`: origin forwarded on `mode: "now"`,
    queued on `mode: "next-heartbeat"`, default fallback when
    origin omitted, whitespace-only origin treated as omitted,
    empty text still rejected.
  - `src/gateway/protocol/index.test.ts` — extends
    `validateWakeParams` coverage with the new optional fields
    accepted + empty-string rejected.

Out of scope (deliberate split):

  - Channel/thread/topic capture on the job's `delivery` block —
    follow-up PR once this contract lands. The minimum-viable fix
    here is session routing, which unblocks the CLI runtime's
    `--resume <session>` path and the embedded session-resolution
    path without a schema rewrite of the delivery contract.
  - The 5 stalled wake-related PRs (openclaw#70268, openclaw#57199, openclaw#82767,
    openclaw#79869, openclaw#63096) each fix downstream specifics. This PR fixes
    the upstream origin-capture they all silently assume.
anagnorisis2peripeteia added a commit to anagnorisis2peripeteia/openclaw that referenced this pull request May 19, 2026
The cron `wake` MCP tool currently forwards only `{mode, text}` to the
gateway. Every wake then enqueues a system event with no sessionKey /
agentId, so the cron service falls back to the heartbeat / main
default. Wakes scheduled from a non-main session (Telegram thread,
Discord channel, multi-agent setup) silently route to the wrong
conversation lane — and on CLI runtimes the woken session burns
tokens generating output that no caller can route back.

Origin capture (closes the upstream half of openclaw#46886 and openclaw#64556):

  - `src/agents/tools/cron-tool.ts` — the `wake` case now resolves
    `opts.agentSessionKey` through `resolveInternalSessionKey` and
    `resolveSessionAgentId`, matching the existing `add` action at
    L569-583. Explicit `sessionKey` / `agentId` params on the tool
    call take precedence over the inferred values so cross-session
    wakes remain expressible.
  - `src/gateway/protocol/schema/agent.ts` — `WakeParamsSchema` now
    declares optional `sessionKey` and `agentId` (NonEmptyString)
    so the gateway-level validator types them explicitly. The
    schema's `additionalProperties: true` continues to accept
    forward-compat metadata unchanged.
  - `src/gateway/server-methods/cron.ts` — the wake handler reads
    both fields, trims them, and forwards to `context.cron.wake`.
  - `src/cron/service.ts` + `src/cron/service/ops.ts` +
    `src/cron/service/timer.ts` — `wake()` accepts optional
    `sessionKey`/`agentId` and threads them into
    `enqueueSystemEvent` and `requestHeartbeatNow`. Missing /
    whitespace-only fields fall through to the dep's default so
    pre-existing call sites with no origin keep behaving the same
    way (backwards compatible).

Tests:

  - `src/cron/service/wake-origin.test.ts` (new, 6 tests) —
    direct seams on `wake()`: origin forwarded on `mode: "now"`,
    queued on `mode: "next-heartbeat"`, default fallback when
    origin omitted, whitespace-only origin treated as omitted,
    empty text still rejected.
  - `src/gateway/protocol/index.test.ts` — extends
    `validateWakeParams` coverage with the new optional fields
    accepted + empty-string rejected.

Out of scope (deliberate split):

  - Channel/thread/topic capture on the job's `delivery` block —
    follow-up PR once this contract lands. The minimum-viable fix
    here is session routing, which unblocks the CLI runtime's
    `--resume <session>` path and the embedded session-resolution
    path without a schema rewrite of the delivery contract.
  - The 5 stalled wake-related PRs (openclaw#70268, openclaw#57199, openclaw#82767,
    openclaw#79869, openclaw#63096) each fix downstream specifics. This PR fixes
    the upstream origin-capture they all silently assume.
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 22, 2026
@clawsweeper

clawsweeper Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 22, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 7, 2026
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. size: L stale Marked as stale due to inactivity triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants