Skip to content

fix(hooks): flag hook event names that no core trigger emits#99456

Merged
vincentkoc merged 4 commits into
openclaw:mainfrom
masatohoshino:fix/hooks-unknown-event-validation
Jul 6, 2026
Merged

fix(hooks): flag hook event names that no core trigger emits#99456
vincentkoc merged 4 commits into
openclaw:mainfrom
masatohoshino:fix/hooks-unknown-event-validation

Conversation

@masatohoshino

Copy link
Copy Markdown
Contributor

What Problem This Solves

A hook's HOOK.md frontmatter declares which internal events it subscribes to (metadata.openclaw.events), but the loader accepts any string: registerInternalHook(eventKey) registers arbitrary keys, and only an empty events array produces a warning (src/hooks/loader.ts). Since core is the only emitter of internal hook events, a misspelled name (command:nwe, message:recieved, gateway:started) loads "successfully" and then silently never fires — no loader warning, nothing in openclaw hooks list/info, no way to tell a dead subscription from a hook that simply hasn't triggered yet. Debugging "my hook never runs" today means reading core source to discover the valid key set.

Why This Change Was Made

The core-emitted key set is small, closed, and already documented (the events table in docs/automation/hooks.md): core trigger sites emit exactly 14 family:action keys, and hooks may also subscribe to a bare family (command, session, agent, gateway, message) to receive every action. Plugins can emit additional keys through the deprecated plugin-sdk/hook-runtime barrel (all bundled plugins emit only known keys — Telegram/Slack/Signal/WhatsApp emit message:received/message:sent), so a name outside the core set is almost always a typo — the diagnostics say so honestly ("not emitted by OpenClaw core — likely a typo; unless a plugin emits it, the hook never fires") and stay strictly advisory.

This change makes that set checkable:

  • KNOWN_INTERNAL_HOOK_EVENT_KEYS + isKnownInternalHookEventKey in src/hooks/internal-hook-types.ts (colocated with the InternalHookEventType union; the list mirrors the docs table and each entry was verified against its createInternalHookEvent call site, including the dynamic ones — performGatewaySessionReset emits "new" | "reset", reset-command hooks emit ResetCommandAction = "new" | "reset").
  • The loader warns when a hook declares events outside the core-emitted set, naming the hook, the keys, and the docs URL — for both directory-discovered HOOK.md hooks and the legacy hooks.internal.handlers config shape. The hook is still registered unchanged — validation is advisory, zero behavior change, so plugin-emitted custom events and version skew degrade to today's behavior plus a warning rather than a refusal to load.
  • HookStatusEntry gains unknownEvents; openclaw hooks info <name> renders a ⚠ Event not emitted by core (likely typo): … line and the JSON report includes the field, so the diagnosis is visible after load time too.
  • docs/automation/hooks.md documents the behavior next to the events table.

User Impact

Hook authors find out about dead subscriptions immediately (gateway log at load) and on demand (openclaw hooks info), instead of shipping a hook that silently does nothing. Existing hooks with valid events see no change; hooks with typos keep loading exactly as before but are no longer silent about it.

Evidence

HEAD: f9b756725354debd4aa64cc62b36e411245b8e00 (base upstream/main df350e6720)

Real behavior proof (no mocks — a real HOOK.md declaring ["command:nwe", "command:new"] loaded through the production loadInternalHooks, then rendered by the production status builder and CLI; captured at 607ca0ff76 — the one commit since, f9b75672, only adds the same advisory to the legacy hooks.internal.handlers path and does not touch the surfaces proven here):

=== 1. production loadInternalHooks over a real HOOK.md (watch the warning) ===
[hooks:loader] Hook 'typo-hook' subscribes to event command:nwe not emitted by OpenClaw core — likely a typo; unless a plugin emits it, the hook never fires. Known events: https://docs.openclaw.ai/automation/hooks
loaded hooks: 1; registered keys: command:nwe, command:new

=== 2. production hooks status report ===
hook=typo-hook events=[command:nwe, command:new] unknownEvents=[command:nwe]

=== 3. production `openclaw hooks info` rendering ===
  Events: command:nwe, command:new
  ⚠ Event not emitted by core (likely typo): command:nwe

Focused tests (all green locally):

  • src/hooks/internal-hook-types.test.ts (new) — accept matrix for all 14 keys + 5 bare families; reject matrix for typos, bare actions, unknown families, case mismatches
  • src/hooks/loader.test.ts — typo'd key still registers (advisory) and the valid subscription keeps firing; legacy config handlers get the same advisory treatment
  • src/cli/hooks-cli.test.tshooks info renders the ⚠ line
  • src/commands/onboard-hooks.test.ts — fixture updated for the new required field
  • pnpm tsgo:core + pnpm tsgo:core:test clean

External structured review: Codex (GPT-5.5), 5 rounds — flagged docs wording (bare-family subscriptions), an overclaim in this body ("plugins cannot emit new keys" vs. the deprecated plugin-sdk/hook-runtime re-export → diagnostics reworded to "not emitted by core / likely a typo"), and a missed sibling seam (the legacy hooks.internal.handlers path registered events without the advisory check → covered); final pass clean.

Known limits / Non-scope: advisory only — no fail-closed loading, no doctor check (the hooks CLI is the existing diagnostic surface), no change to the separate plugin-SDK hook API, no fuzzy matching/normalization of event names; no attempt to track plugin-emitted custom keys. New core trigger sites must add their key to the list + docs table (called out in a code comment at the list).


🤖 AI-assisted (Claude Fable 5 + Codex GPT-5.5 review loop). Prompt lineage: translate agentic-coding-tool hook-validation patterns (Claude Code hook-matcher fixes) into OpenClaw's internal hook loader; implementation, tests, review rounds, and the real-behavior proof were run locally as shown above.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation cli CLI command changes commands Command implementations size: S labels Jul 3, 2026
@clawsweeper

clawsweeper Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 3, 2026, 5:14 AM ET / 09:14 UTC.

Summary
The PR adds a known internal hook event registry, advisory unknown-event warnings, an unknownEvents hook status/CLI field, focused tests, and hooks documentation.

PR surface: Source +77, Tests +103, Docs +8. Total +188 across 9 files.

Reproducibility: yes. source-reproducible: current main registers every declared event string, while runtime dispatch only looks up the emitted family and type:action keys. I did not execute a local repro because this review is read-only.

Review metrics: 1 noteworthy metric.

  • Known hook event registry: 14 specific keys + 5 bare families added. This new manual source of truth controls loader and status diagnostics for unknown hook event names.

Stored data model
Persistent data-model change detected: serialized state: src/hooks/loader.test.ts, vector/embedding metadata: src/hooks/loader.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

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

Rank-up moves:

  • none.

Risk before merge

  • [P1] The known-event registry is manually maintained, so future core trigger sites must update the registry and hooks documentation together.

Maintainer options:

  1. Decide the mitigation before merge
    Land the advisory diagnostic if maintainers accept the manual core event list, keeping trigger sites, the registry, and docs in sync.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No ClawSweeper repair is needed because the PR already contains a focused implementation and no actionable review finding.

Security
Cleared: The diff adds no dependencies, workflows, scripts, permissions, or secret-handling changes; it only adds advisory diagnostics and tests.

Review details

Best possible solution:

Land the advisory diagnostic if maintainers accept the manual core event list, keeping trigger sites, the registry, and docs in sync.

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

Yes, source-reproducible: current main registers every declared event string, while runtime dispatch only looks up the emitted family and type:action keys. I did not execute a local repro because this review is read-only.

Is this the best way to solve the issue?

Yes, advisory validation in loader/status is the best scoped fix because plugin-emitted custom events remain possible and fail-closed validation would be compatibility-sensitive.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 68e06b9ea148.

Label changes

Label justifications:

  • P3: This is a low-risk hooks CLI/runtime diagnostic improvement for misconfigured hook authors, not an urgent runtime breakage.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes terminal output from a real HOOK.md loaded through production loader/status/CLI paths showing the new warning and CLI diagnostic.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal output from a real HOOK.md loaded through production loader/status/CLI paths showing the new warning and CLI diagnostic.
Evidence reviewed

PR surface:

Source +77, Tests +103, Docs +8. Total +188 across 9 files.

View PR surface stats
Area Files Added Removed Net
Source 4 77 0 +77
Tests 4 104 1 +103
Docs 1 8 0 +8
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 9 189 1 +188

What I checked:

  • Current loader behavior: Current main only warns when a discovered hook has an empty events array, then registers every declared event string unchanged. (src/hooks/loader.ts:164, 68e06b9ea148)
  • Runtime dispatch behavior: triggerInternalHook only dispatches handlers registered for the event family or the emitted type:action key, so a typo such as command:nwe will not fire for command:new. (src/hooks/internal-hooks.ts:286, 68e06b9ea148)
  • Current status gap: Current main reports declared events but has no unknownEvents diagnostic field, and hook info prints the events without warning about unsupported names. (src/hooks/hooks-status.ts:26, 68e06b9ea148)
  • Documented event surface: The hooks documentation already presents a small event table for supported internal hook event names. Public docs: docs/automation/hooks.md. (docs/automation/hooks.md:46, 68e06b9ea148)
  • PR implementation proof: The PR diff adds the known-event helper, loader advisories for discovered and legacy handlers, unknownEvents status/CLI output, tests, and documentation. (f9b756725354)
  • History and ownership signals: Recent hook loader/status/docs history points primarily to steipete, vincentkoc, and Drickon as relevant routing candidates for this surface. (src/hooks/loader.ts, 31de0335908b)

Likely related people:

  • steipete: Recent hook loader, status, and docs commits include hook contract preservation, hook policy resolution, hook helper docs, and message-hook bridge work. (role: recent area contributor; confidence: high; commits: 82dae95c762a, 5cb2f4558573, 4c3b4f8ad8f8; files: src/hooks/loader.ts, src/hooks/hooks-status.ts, src/hooks/internal-hooks.ts)
  • vincentkoc: Recent history shows focused work on hook loader path handling, cycle extraction around hook types, and hook docs/status adjacency. (role: hook loader contributor; confidence: medium; commits: 31de0335908b, d4f535b20302, 97e64196a01d; files: src/hooks/loader.ts, src/hooks/internal-hooks.ts, docs/automation/hooks.md)
  • Drickon: History shows prior fixes to the internal hook registry singleton and message hook contexts, which are adjacent to this PR's event-key matching logic. (role: internal hook registry contributor; confidence: medium; commits: 0d8beeb4e5f5, b5102ba4f98e, e0b8b80067cf; files: src/hooks/internal-hooks.ts)
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.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. labels Jul 3, 2026
@vincentkoc
vincentkoc merged commit e7ca90e into openclaw:main Jul 6, 2026
155 of 161 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 7, 2026
…w#99456)

* fix(hooks): flag hook event names that no trigger site emits

* docs(hooks): clarify bare family subscriptions in unknown-event note

* fix(hooks): word unknown-event diagnostics around core-emitted keys

* fix(hooks): apply unknown-event advisory to legacy config handlers too
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…w#99456)

* fix(hooks): flag hook event names that no trigger site emits

* docs(hooks): clarify bare family subscriptions in unknown-event note

* fix(hooks): word unknown-event diagnostics around core-emitted keys

* fix(hooks): apply unknown-event advisory to legacy config handlers too
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes commands Command implementations docs Improvements or additions to documentation P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants