-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(telemetry): Phase 2 — tool.blocked_on_user + hook spans (#3731) #4321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
38ba22d
6767469
32f94d3
68dea8a
2b227b0
cc3f7fc
f9ff554
eafe688
574f645
48e78d6
9cbbdfc
31921a9
fc509d5
f0befac
8716069
e7dd8aa
51cb97c
84851f2
2c268a8
a1d1190
ac7597e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,6 +43,14 @@ export interface PreToolUseHookResult { | |||||||||
| blockType?: 'denied' | 'ask' | 'stop'; | ||||||||||
| /** Additional context to add */ | ||||||||||
| additionalContext?: string; | ||||||||||
| /** | ||||||||||
| * Set when the hook helper caught and absorbed a transport / dispatch | ||||||||||
| * error. The tool execution still proceeds (existing non-blocking | ||||||||||
| * contract), but observers (telemetry spans, debug logs) can detect | ||||||||||
| * that the hook itself failed instead of treating the safe-default | ||||||||||
| * response as a successful "allow" decision (#4321 review). | ||||||||||
| */ | ||||||||||
| hookError?: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /** | ||||||||||
|
|
@@ -55,6 +63,8 @@ export interface PostToolUseHookResult { | |||||||||
| stopReason?: string; | ||||||||||
| /** Additional context to append to tool response */ | ||||||||||
| additionalContext?: string; | ||||||||||
| /** See PreToolUseHookResult.hookError. */ | ||||||||||
| hookError?: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /** | ||||||||||
|
|
@@ -63,6 +73,8 @@ export interface PostToolUseHookResult { | |||||||||
| export interface PostToolUseFailureHookResult { | ||||||||||
| /** Additional context about the failure */ | ||||||||||
| additionalContext?: string; | ||||||||||
| /** See PreToolUseHookResult.hookError. */ | ||||||||||
| hookError?: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /** | ||||||||||
|
|
@@ -107,7 +119,27 @@ export async function firePreToolUseHook( | |||||||||
| ); | ||||||||||
|
|
||||||||||
| if (!response.success || !response.output) { | ||||||||||
| return { shouldProceed: true }; | ||||||||||
| // Hook runner reported failure (URL validation, fn exception, | ||||||||||
| // prompt-runner crash, ...). The `response.error` from the runner | ||||||||||
| // is the canonical cause — forward it so telemetry and operators | ||||||||||
| // see the actual failure instead of a fake "allow" success | ||||||||||
| // (#4321 review silent-failure-hunter HIGH). | ||||||||||
| // | ||||||||||
| // If runner returned `{ success: false }` (or missing output) with no | ||||||||||
| // `error.message`, synthesize a sentinel so the contract violation is | ||||||||||
| // still visible on the span instead of silently degrading to an allow | ||||||||||
| // with empty telemetry (#4321 review-7 silent-failure-hunter HIGH-1). | ||||||||||
| // `||` (revert from `??`): downstream consumers in | ||||||||||
| // coreToolScheduler.ts gate on `r.hookError ? ...`, so an | ||||||||||
| // empty-string message would be silently dropped — the previous | ||||||||||
| // `??` change defeated its own intent. Empty-string error | ||||||||||
| // messages carry no operator value; the sentinel is more | ||||||||||
| // actionable. (#4321 review-9 wenshao Suggestion refines | ||||||||||
| // review-8.) | ||||||||||
| const message = | ||||||||||
| response.error?.message || | ||||||||||
| `hook runner returned ${response.success ? 'no output' : 'success: false'} without error detail`; | ||||||||||
| return { shouldProceed: true, hookError: message }; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const preToolOutput = createHookOutput( | ||||||||||
|
|
@@ -155,10 +187,9 @@ export async function firePreToolUseHook( | |||||||||
| }; | ||||||||||
| } catch (error) { | ||||||||||
| // Hook errors should not block tool execution | ||||||||||
| debugLogger.warn( | ||||||||||
| `PreToolUse hook error for ${toolName}: ${error instanceof Error ? error.message : String(error)}`, | ||||||||||
| ); | ||||||||||
| return { shouldProceed: true }; | ||||||||||
| const message = error instanceof Error ? error.message : String(error); | ||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Catch blocks construct Same issue at lines 277 (
Suggested change
— qwen-latest-series-invite-beta-v34 via Qwen Code /review |
||||||||||
| debugLogger.warn(`PreToolUse hook error for ${toolName}: ${message}`); | ||||||||||
| return { shouldProceed: true, hookError: message }; | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
@@ -207,7 +238,18 @@ export async function firePostToolUseHook( | |||||||||
| ); | ||||||||||
|
|
||||||||||
| if (!response.success || !response.output) { | ||||||||||
| return { shouldStop: false }; | ||||||||||
| // See firePreToolUseHook for the rationale. | ||||||||||
| // `||` (revert from `??`): downstream consumers in | ||||||||||
| // coreToolScheduler.ts gate on `r.hookError ? ...`, so an | ||||||||||
| // empty-string message would be silently dropped — the previous | ||||||||||
| // `??` change defeated its own intent. Empty-string error | ||||||||||
| // messages carry no operator value; the sentinel is more | ||||||||||
| // actionable. (#4321 review-9 wenshao Suggestion refines | ||||||||||
| // review-8.) | ||||||||||
| const message = | ||||||||||
| response.error?.message || | ||||||||||
| `hook runner returned ${response.success ? 'no output' : 'success: false'} without error detail`; | ||||||||||
| return { shouldStop: false, hookError: message }; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const postToolOutput = createHookOutput( | ||||||||||
|
|
@@ -232,10 +274,9 @@ export async function firePostToolUseHook( | |||||||||
| }; | ||||||||||
| } catch (error) { | ||||||||||
| // Hook errors should not affect tool result | ||||||||||
| debugLogger.warn( | ||||||||||
| `PostToolUse hook error for ${toolName}: ${error instanceof Error ? error.message : String(error)}`, | ||||||||||
| ); | ||||||||||
| return { shouldStop: false }; | ||||||||||
| const message = error instanceof Error ? error.message : String(error); | ||||||||||
| debugLogger.warn(`PostToolUse hook error for ${toolName}: ${message}`); | ||||||||||
| return { shouldStop: false, hookError: message }; | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
@@ -287,7 +328,18 @@ export async function firePostToolUseFailureHook( | |||||||||
| ); | ||||||||||
|
|
||||||||||
| if (!response.success || !response.output) { | ||||||||||
| return {}; | ||||||||||
| // See firePreToolUseHook for the rationale. | ||||||||||
| // `||` (revert from `??`): downstream consumers in | ||||||||||
| // coreToolScheduler.ts gate on `r.hookError ? ...`, so an | ||||||||||
| // empty-string message would be silently dropped — the previous | ||||||||||
| // `??` change defeated its own intent. Empty-string error | ||||||||||
| // messages carry no operator value; the sentinel is more | ||||||||||
| // actionable. (#4321 review-9 wenshao Suggestion refines | ||||||||||
| // review-8.) | ||||||||||
| const message = | ||||||||||
| response.error?.message || | ||||||||||
| `hook runner returned ${response.success ? 'no output' : 'success: false'} without error detail`; | ||||||||||
| return { hookError: message }; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const failureOutput = createHookOutput( | ||||||||||
|
|
@@ -301,10 +353,11 @@ export async function firePostToolUseFailureHook( | |||||||||
| }; | ||||||||||
| } catch (error) { | ||||||||||
| // Hook errors should not affect error handling | ||||||||||
| const message = error instanceof Error ? error.message : String(error); | ||||||||||
| debugLogger.warn( | ||||||||||
| `PostToolUseFailure hook error for ${toolName}: ${error instanceof Error ? error.message : String(error)}`, | ||||||||||
| `PostToolUseFailure hook error for ${toolName}: ${message}`, | ||||||||||
| ); | ||||||||||
| return {}; | ||||||||||
| return { hookError: message }; | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
||||||||||
Uh oh!
There was an error while loading. Please reload this page.