Skip to content

fix: pass session context to plugin tool hooks in toToolDefinitions#19422

Closed
namabile wants to merge 1 commit intoopenclaw:mainfrom
namabile:fix/plugin-tool-hook-session-context
Closed

fix: pass session context to plugin tool hooks in toToolDefinitions#19422
namabile wants to merge 1 commit intoopenclaw:mainfrom
namabile:fix/plugin-tool-hook-session-context

Conversation

@namabile
Copy link

@namabile namabile commented Feb 17, 2026

Summary

Fixes #19381

Plugin-registered tools that go through toToolDefinitions without being pre-wrapped by wrapToolWithBeforeToolCallHook receive undefined for both agentId and sessionKey in their before_tool_call and after_tool_call hooks, making per-session access control impossible.

Root cause

toToolDefinitions has a fallback path (line 103-108) for unwrapped tools that calls runBeforeToolCallHook without passing ctx. Additionally, after_tool_call hooks (both success and error paths) only pass { toolName } as context, never agentId/sessionKey.

Changes

  • src/agents/pi-tool-definition-adapter.ts: Accept optional hookContext parameter in toToolDefinitions. Pass it to runBeforeToolCallHook for unwrapped tools and include agentId/sessionKey in all after_tool_call hook contexts.
  • src/agents/pi-embedded-runner/tool-split.ts: Forward hookContext through splitSdkTools to toToolDefinitions.
  • src/agents/pi-embedded-runner/run/attempt.ts: Pass sessionAgentId/sessionKey as hook context to splitSdkTools.
  • src/agents/pi-embedded-runner/compact.ts: Pass sessionAgentId/sessionKey as hook context to splitSdkTools.
  • src/agents/pi-tool-definition-adapter.hook-context.test.ts: 4 regression tests covering:
    • before_tool_call receives agentId/sessionKey for unwrapped tools
    • after_tool_call receives context on success
    • after_tool_call receives context on error
    • Backwards compatibility when no hookContext is provided

Test plan

  • All 4 new regression tests pass
  • All 395 existing agent tests pass (65 test files)
  • pnpm format:check — clean
  • pnpm tsgo — clean
  • pnpm lint — 0 warnings, 0 errors

Greptile Summary

This PR correctly fixes the root cause described in issue #19381: plugin tools processed through toToolDefinitions without being pre-wrapped by wrapToolWithBeforeToolCallHook were receiving undefined for agentId and sessionKey in their before_tool_call and after_tool_call hooks, making per-session access control impossible. The fix threads an optional hookContext parameter through splitSdkToolstoToolDefinitions and wires it into all three hook invocation sites. Both call sites (attempt.ts and compact.ts) are updated consistently.

Key observations:

  • The core fix is correct and the approach is clean and backward-compatible (optional parameter with undefined fallback).
  • There is a pre-existing inconsistency now made more consequential: the after_tool_call success path passes the raw, un-normalized tool name as toolName in both event payload and context (lines 129, 134), while the error path passes normalizedName (lines 179, 184). Since plugins can now rely on toolName in the hook context for access control routing, receiving different values for the same tool depending on success vs. failure is a correctness hazard.
  • The regression tests use all-lowercase tool names ("my_plugin_tool", "failing_tool"), which are unchanged by normalizeToolName, so the name vs. normalizedName discrepancy is not caught.

Confidence Score: 3/5

  • Safe to merge with one logic issue that should be addressed before plugins rely on consistent toolName values in after_tool_call contexts.
  • The fix correctly addresses the reported bug and is backward-compatible. However, a pre-existing inconsistency — raw name on the success path vs normalizedName on the error path in after_tool_call — becomes a real correctness hazard now that hookContext is plumbed through and plugins can perform access control using toolName. The regression tests don't cover this edge case. This warrants attention before the feature is relied upon in production plugins.
  • src/agents/pi-tool-definition-adapter.ts (lines 129 and 134: success-path after_tool_call uses raw name instead of normalizedName)

Last reviewed commit: 4ec8a93

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: S labels Feb 17, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 17, 2026

Additional Comments (1)

src/agents/pi-tool-definition-adapter.ts
Inconsistent toolName between success and error after_tool_call contexts

The success path passes the raw (un-normalized) name as toolName in both the event and context objects (lines 129, 134), while the error path passes normalizedName (lowercased/aliased) at lines 179 and 184.

A plugin hook that keys on toolName for per-session access control — the primary use case this PR enables — will receive different values for the same tool depending on whether execution succeeded or failed. For example, a tool named "MyPluginTool" would appear as "MyPluginTool" on success but as "mypluginTool" (or its alias) on error.

The regression tests don't expose this because "my_plugin_tool" and "failing_tool" are already lowercase and unchanged by normalization.

Consider using normalizedName consistently on the success path to match the error path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-tool-definition-adapter.ts
Line: 129:137

Comment:
**Inconsistent `toolName` between success and error `after_tool_call` contexts**

The success path passes the raw (un-normalized) `name` as `toolName` in both the event and context objects (lines 129, 134), while the error path passes `normalizedName` (lowercased/aliased) at lines 179 and 184.

A plugin hook that keys on `toolName` for per-session access control — the primary use case this PR enables — will receive different values for the same tool depending on whether execution succeeded or failed. For example, a tool named `"MyPluginTool"` would appear as `"MyPluginTool"` on success but as `"mypluginTool"` (or its alias) on error.

The regression tests don't expose this because `"my_plugin_tool"` and `"failing_tool"` are already lowercase and unchanged by normalization.

Consider using `normalizedName` consistently on the success path to match the error path.

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

@namabile namabile force-pushed the fix/plugin-tool-hook-session-context branch from 4ec8a93 to 0c6e4b8 Compare February 17, 2026 19:55
…penclaw#19381)

Plugin-registered tools that go through toToolDefinitions without being
wrapped by wrapToolWithBeforeToolCallHook received undefined agentId and
sessionKey in their before_tool_call and after_tool_call hooks, breaking
per-session access control in plugin hooks.

Thread hookContext (agentId, sessionKey) through toToolDefinitions →
splitSdkTools → embedded runner callers so the adapter passes session
identity to runBeforeToolCallHook and runAfterToolCall for all tools.

Closes openclaw#19381
@namabile namabile force-pushed the fix/plugin-tool-hook-session-context branch from 0c6e4b8 to 16dd9d6 Compare February 17, 2026 22:17
@openclaw-barnacle
Copy link

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 Feb 28, 2026
@vincentkoc vincentkoc self-assigned this Mar 2, 2026
@vincentkoc
Copy link
Member

vincentkoc commented Mar 2, 2026

Linking for consolidation context: #32201 PR for embedded after_tool_call context + single-fire behavior.

This PR remains relevant follow-up scope for broader plugin tool-hook context propagation

Link: #32201

@vincentkoc
Copy link
Member

Closing as superseded/obsolete.

Why:

  • Option A was merged in fix(hooks): consolidate after_tool_call context + single-fire behavior #32201.
  • Current main wraps plugin tools with wrapToolWithBeforeToolCallHook(...) in the shared tool path, so hook context propagation for plugin tools is already covered by upstream wiring.
  • The adapter-side after_tool_call dispatch that this PR also touched was removed as part of the single-fire consolidation.

Keeping this as historical reference:
#32201

@vincentkoc vincentkoc closed this Mar 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: before_tool_call hook receives undefined sessionKey and agentId for plugin-registered tools

2 participants