Skip to content

fix(hooks): suppress silent shared-hook fallback while preserving error surfacing#1502

Open
BingqingLyu wants to merge 7 commits into
mainfrom
fork-pr-55761-fix-hooks-announce-policy-upstream
Open

fix(hooks): suppress silent shared-hook fallback while preserving error surfacing#1502
BingqingLyu wants to merge 7 commits into
mainfrom
fork-pr-55761-fix-hooks-announce-policy-upstream

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Fixes openclaw#55704

Summary

This PR fixes the shared-hook fallback seam in dispatchAgentHook.

Today, successful hook outcomes can still fall back into the main session based on !result.delivered, which is too coarse for shared hooks. In practice this can leak silent/no-op hook work into the main session even when the hook lane was intended to stay quiet.

This change replaces the old one-bit success-path check with a small compatibility bridge that:

  • keeps successful deliver:false shared hooks silent
  • avoids duplicate fallback when delivery already happened
  • avoids fallback when delivery was already attempted
  • still surfaces real run-level hook errors, including non-throw status:"error" results

It also cleans up the generic fallback label so default hook names no longer render as Hook Hook.

Why

This is a broader hooks-local generalization of the narrower deliver:false slice tracked in openclaw#36325.

Related lineage:

This PR also addresses the review concern on openclaw#49234: the narrow value.deliver !== false guard can hide real non-throw hook failures. This patch preserves error observability by always surfacing status !== "ok" results.

What changed

Production code

  • Added shouldAnnounceHookResultToMain(...) in src/gateway/server/hooks.ts
  • Replaced the old if (!result.delivered) fallback gate with a compatibility-bridge decision:
    • result.status !== "ok" -> always surface (errors override everything, including explicit announceToMain: false)
    • explicit result-level announceToMain policy can win when present
    • successful deliver:false hooks stay silent
    • result.delivered === true -> no duplicate fallback
    • result.deliveryAttempted === true -> no fallback
    • otherwise preserve old fallback behavior
  • Added formatHookPrefix(...) to fix the Hook Hook label in both success and catch paths
  • Added announceToMain?: boolean as a forward-compatible field on RunCronAgentTurnResult while preserving upstream delivery?: CronDeliveryTrace

Tests

Added 8 targeted gateway hook tests covering:

  • deliver:false success via direct /hooks/agent -> no main-session fallback
  • deliver:false success via mapped hook (Gmail normalizer pattern) -> no main-session fallback
  • deliver:false non-throw status:"error" -> still surfaces
  • deliveryAttempted:true + delivered:false -> no duplicate fallback
  • explicit announceToMain:true -> fallback still works when requested
  • announceToMain:false + narration/trailing NO_REPLY -> suppressed
  • announceToMain:false + status:"error" -> error still surfaces (documents the override)
  • generic default name -> Hook: ... not Hook Hook: ...

Tests use a deterministic async flush helper (flushHookDispatch) instead of fixed-delay setTimeout sleeps for negative assertions.

Rebase refresh

Refreshed onto current main on 2026-04-23. The src/cron/isolated-agent/run.types.ts conflict was additive: the rebased branch keeps both announceToMain?: boolean and upstream delivery?: CronDeliveryTrace.

Test plan

Passed locally after the rebase:

pnpm test src/gateway/server/hooks.agent-trust.test.ts
pnpm test src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts
pnpm test src/gateway/server.hooks.test.ts -t "hook announcement policy"
NODE_OPTIONS=--max-old-space-size=8192 pnpm exec tsc --noEmit

Also attempted pnpm check locally. It passed prod typecheck, lint, conflict-marker, import-cycle, and related guards, then failed in scripts/check-no-runtime-action-load-config.mjs on this Windows workspace because the guard constructed H:\H:\Code\OpenClaw\openclaw\extensions. CI is rerunning on the force-pushed head.

…or surfacing

The shared-hook fallback in dispatchAgentHook previously keyed off
!result.delivered alone to decide whether to inject a system event into
the main session. This conflates outward delivery state with
main-session announcement policy, causing silent/no-op hooks
(deliver:false) and delivery-attempted hooks to leak fallback summaries
into main.

Replace the single-boolean gate with a compatibility-bridge helper
(shouldAnnounceHookResultToMain) that respects:

- result.status !== "ok" → always surface errors (even if
  announceToMain is false — errors override explicit suppression)
- result.announceToMain (explicit policy, forward-compatible)
- value.deliver === false → silent on success
- result.delivered → no duplicate
- result.deliveryAttempted → no fallback for async-ack paths

Also fix the generic fallback label (both success and catch paths) so
default name "Hook" no longer renders as "Hook Hook".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Shared hook fallback can surface silent hook output into the main session

2 participants