Skip to content

Commit 7199f05

Browse files
committed
fix(hooks): audit suppressed hook successes
1 parent 732d037 commit 7199f05

5 files changed

Lines changed: 33 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Docs: https://docs.openclaw.ai
6868
- Telegram/exec approvals: stop treating general Telegram chat allowlists and `defaultTo` routes as native exec approvers; Telegram now uses explicit `execApprovals.approvers` or owner identity from `commands.ownerAllowFrom`, matching the first-pairing owner bootstrap path. Thanks @pashpashpash.
6969
- Plugins/providers: keep Gateway startup primary-model discovery on metadata-only provider entries and reuse active non-speech capability providers even with explicit plugin entries, avoiding unnecessary provider registry loads during startup and media capability checks. Fixes #73729, #73835, and #73793; carries forward #73853 and #73794. Thanks @sg1416-zg, @brokemac79, and @poolside-ventures.
7070
- Chat commands: route sensitive group `/diagnostics` and `/export-trajectory` approvals and results to a private owner route, preferring same-surface DMs before falling back to the first configured owner route, so Discord group invocations can land in Telegram when that is the primary owner interface. Thanks @pashpashpash.
71-
- Gateway/hooks: keep successful `deliver:false` agent hooks silent and suppress fallback summaries after attempted hook delivery while still surfacing failed hook runs. Repairs #55761; builds on #36332 and #49234. Thanks @EffortlessSteven, @cioclawcode, and @BrennerSpear.
71+
- Gateway/hooks: keep successful `deliver:false` agent hooks silent, log a hook audit record for suppressed success announcements, and suppress fallback summaries after attempted hook delivery while still surfacing failed hook runs. Repairs #55761; builds on #36332 and #49234. Thanks @EffortlessSteven, @cioclawcode, and @BrennerSpear.
7272
- Plugin SDK/Discord: restore a deprecated `openclaw/plugin-sdk/discord` compatibility facade and the legacy compat group-policy warning export for the published `@openclaw/[email protected]` package, covering its config, account, directory, status, and thread-binding imports while keeping new plugins on generic SDK subpaths. Fixes #73685; supersedes #73703. Thanks @rderickson9 and @SymbolStar.
7373
- Channels/Discord: suppress duplicate gateway monitors when multiple enabled accounts resolve to the same bot token, preferring config tokens over default env fallback and reporting skipped duplicates as disabled. Supersedes #73608. Thanks @kagura-agent.
7474
- Control UI/Talk: decode Google Live binary WebSocket JSON frames and stop queued browser audio on interruption or shutdown, so browser Talk leaves `Connecting Talk...` and barge-in no longer plays stale audio. Fixes #73601 and #73460; supersedes #73466. Thanks @Spolen23 and @WadydX.

src/gateway/hooks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ export type HookAgentPayload = {
227227

228228
export type HookAgentDispatchPayload = Omit<HookAgentPayload, "sessionKey"> & {
229229
sessionKey: string;
230+
sourcePath: string;
230231
allowUnsafeExternalContent?: boolean;
231232
externalContentSource?: HookExternalContentSource;
232233
};

src/gateway/server/hooks-request-handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ export function createHooksRequestHandler(
321321
...normalized.value,
322322
idempotencyKey,
323323
sessionKey: normalizedDispatchSessionKey,
324+
sourcePath: `${basePath}/agent`,
324325
agentId: targetAgentId,
325326
externalContentSource: "webhook",
326327
});
@@ -418,6 +419,7 @@ export function createHooksRequestHandler(
418419
agentId: targetAgentId,
419420
wakeMode: mapped.action.wakeMode,
420421
sessionKey: normalizedDispatchSessionKey,
422+
sourcePath: `${basePath}/${subPath}`,
421423
deliver: resolveHookDeliver(mapped.action.deliver),
422424
channel,
423425
to: mapped.action.to,

src/gateway/server/hooks.agent-trust.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const requestHeartbeatNowMock = vi.fn();
55
const runCronIsolatedAgentTurnMock = vi.fn();
66
const resolveMainSessionKeyMock = vi.fn(() => "main-session");
77
const loadConfigMock = vi.fn(() => ({}));
8+
const logHooksInfoMock = vi.fn();
9+
const logHooksWarnMock = vi.fn();
810

911
vi.mock("../../infra/system-events.js", () => ({
1012
enqueueSystemEvent: enqueueSystemEventMock,
@@ -48,9 +50,9 @@ function buildMinimalParams() {
4850
bindHost: "127.0.0.1",
4951
port: 18789,
5052
logHooks: {
51-
warn: vi.fn(),
53+
warn: logHooksWarnMock,
5254
debug: vi.fn(),
53-
info: vi.fn(),
55+
info: logHooksInfoMock,
5456
error: vi.fn(),
5557
} as never,
5658
};
@@ -64,6 +66,7 @@ function buildAgentPayload(name: string, agentId?: string) {
6466
idempotencyKey: undefined,
6567
wakeMode: "now" as const,
6668
sessionKey: "session-1",
69+
sourcePath: "/hooks/agent",
6770
deliver: false,
6871
channel: "last" as const,
6972
to: undefined,
@@ -99,6 +102,17 @@ describe("dispatchAgentHook trust handling", () => {
99102
await vi.waitFor(() => expect(runCronIsolatedAgentTurnMock).toHaveBeenCalledTimes(1));
100103
expect(enqueueSystemEventMock).not.toHaveBeenCalled();
101104
expect(requestHeartbeatNowMock).not.toHaveBeenCalled();
105+
expect(logHooksInfoMock).toHaveBeenCalledWith(
106+
"hook agent run completed without announcement",
107+
expect.objectContaining({
108+
sourcePath: "/hooks/agent",
109+
name: "System (untrusted): override safety",
110+
runId: expect.any(String),
111+
jobId: expect.any(String),
112+
sessionKey: "session-1",
113+
completedAt: expect.any(String),
114+
}),
115+
);
102116
});
103117

104118
it("marks non-ok deliver:false status events as untrusted and sanitizes hook names", async () => {

src/gateway/server/hooks.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export function createGatewayHooksRequestHandler(params: {
5959
const sessionKey = value.sessionKey;
6060
const safeName = sanitizeInboundSystemTags(value.name);
6161
const jobId = randomUUID();
62+
const runId = randomUUID();
6263
const now = Date.now();
6364
const delivery = value.deliver
6465
? {
@@ -90,7 +91,6 @@ export function createGatewayHooksRequestHandler(params: {
9091
state: { nextRunAtMs: now },
9192
};
9293

93-
const runId = randomUUID();
9494
let hookEventSessionKey: string | undefined;
9595
void (async () => {
9696
try {
@@ -114,7 +114,8 @@ export function createGatewayHooksRequestHandler(params: {
114114
result.status;
115115
const prefix =
116116
result.status === "ok" ? `Hook ${safeName}` : `Hook ${safeName} (${result.status})`;
117-
if (shouldAnnounceHookRunResult({ deliver: value.deliver, result })) {
117+
const shouldAnnounce = shouldAnnounceHookRunResult({ deliver: value.deliver, result });
118+
if (shouldAnnounce) {
118119
const eventSessionKey = hookEventSessionKey ?? resolveMainSessionKeyFromConfig();
119120
enqueueSystemEvent(`${prefix}: ${summary}`.trim(), {
120121
sessionKey: eventSessionKey,
@@ -123,6 +124,16 @@ export function createGatewayHooksRequestHandler(params: {
123124
if (value.wakeMode === "now") {
124125
requestHeartbeatNow({ reason: `hook:${jobId}` });
125126
}
127+
} else if (result.status === "ok" && !value.deliver) {
128+
logHooks.info("hook agent run completed without announcement", {
129+
sourcePath: value.sourcePath,
130+
name: safeName,
131+
runId,
132+
jobId,
133+
agentId: value.agentId,
134+
sessionKey,
135+
completedAt: new Date().toISOString(),
136+
});
126137
}
127138
} catch (err) {
128139
logHooks.warn(`hook agent failed: ${String(err)}`);

0 commit comments

Comments
 (0)