Skip to content

feat(hooks): Implement 10 core event hooks for session lifecycle and tool execution#2203

Merged
DennisYu07 merged 29 commits intomainfrom
feat/hook_sessionstart_sessionend
Mar 19, 2026
Merged

feat(hooks): Implement 10 core event hooks for session lifecycle and tool execution#2203
DennisYu07 merged 29 commits intomainfrom
feat/hook_sessionstart_sessionend

Conversation

@DennisYu07
Copy link
Copy Markdown
Collaborator

@DennisYu07 DennisYu07 commented Mar 9, 2026

TLDR

This PR implements 10 core event hooks for the Qwen Code hook system, enabling extensibility across session lifecycle, tool execution, conversation management, notifications, and subagent operations. Each hook supports command-based execution with parallel/sequential execution modes, matcher filtering, and structured input/output contracts.

Key highlights:

  • Session lifecycle: SessionStart, SessionEnd for session boundary events
  • Tool execution: PreToolUse, PostToolUse, PostToolUseFailure for comprehensive tool call instrumentation
  • Conversation management: PreCompact for compaction hooks
  • Notifications: Notification for system notification interception
  • Subagent operations: SubagentStart, SubagentEnd for agent spawning lifecycle
  • Permission automation: PermissionRequest for automated permission decision-making

Dive Deeper

The hook system follows a pipeline architecture with four main components:

  • HookEventHandler - Event bus that coordinates hook execution across the system
  • HookPlanner - Creates execution plans with matcher-based filtering and sequential/parallel strategies
  • HookRunner - Executes command hooks with timeout handling, input/output parsing, and error recovery
  • HookAggregator - Aggregates results from multiple hooks with event-specific logic
1. Event Fired (HookEventHandler)
   ↓
2. Create Execution Plan (HookPlanner)
   - Filter by matcher (regex matching)
   - Deduplicate identical hooks
   - Determine sequential vs parallel
   ↓
3. Execute Hooks (HookRunner)  
   - Spawn command process
   - Send JSON input via stdin
   - Parse JSON/plain text output
   - Handle timeouts (default 60s)
   ↓
4. Aggregate Results (HookAggregator)
   - Event-specific merge logic
   - Create specialized output classes
   ↓
5. Apply Decisions
   - Block/deny stops execution
   - System messages displayed to user
   - Additional context injected into conversation
image

Reviewer Test Plan

Run Integration Tests:

cd /path/to/qwen-code
npm run test:integration -- integration-tests/hook-integration/hooks.test.ts

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Linked issues / bugs

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 9, 2026

📋 Review Summary

This PR implements a comprehensive hook system with 10 core event hooks for the Qwen Code platform, enabling extensibility across session lifecycle, tool execution, conversation management, notifications, and subagent operations. The implementation follows a well-designed pipeline architecture with proper separation of concerns, though there are some areas for improvement in error handling and consistency.

🔍 General Feedback

  • The hook system architecture is well-designed with clear separation between event handling, planning, execution, and aggregation
  • Good use of TypeScript types and interfaces to define contracts between components
  • Comprehensive test coverage for the new functionality
  • Proper error handling with fallback behaviors when hooks fail
  • Consistent naming conventions and clear documentation throughout the implementation
  • The pipeline architecture with HookEventHandler -> HookPlanner -> HookRunner -> HookAggregator is well-structured

🎯 Specific Feedback

🟡 High

  • File: packages/core/src/hooks/hookRunner.ts:248 - The timeout handling in executeCommandHook could potentially leave child processes running if termination fails. Consider adding a force kill mechanism after a grace period to prevent resource leaks.
  • File: packages/core/src/hooks/hookRunner.ts:185 - The executeCommandHook method has complex error handling logic that could benefit from early returns to reduce nesting and improve readability.

🟢 Medium

  • File: packages/core/src/hooks/hookSystem.ts:105 - The fireSessionStartEvent method has repeated code blocks for different sources. Consider refactoring to extract common logic into a helper function.
  • File: packages/core/src/hooks/hookRunner.ts:104 - The parseHookOutput method could benefit from more specific error messages that include the hook name or ID for easier debugging.
  • File: packages/core/src/hooks/hookAggregator.ts:177 - In aggregateNotificationResults, the if (result.systemMessage) check is duplicated across multiple event handlers. Consider creating a shared function for this common logic.
  • File: packages/core/src/hooks/hookPlanner.ts:111 - The createExecutionPlan method could benefit from more descriptive variable names (e.g., matchingHooks instead of hooks) to improve readability.

🔵 Low

  • File: packages/core/src/hooks/types.ts:11 - Consider adding JSDoc comments to the HookEventName enum values to explain when each event is fired.
  • File: packages/core/src/hooks/hookSystem.ts:142 - The firePreToolUseEvent method could benefit from early returns to reduce nesting in the permission handling logic.
  • File: packages/core/src/hooks/hookRunner.ts:148 - Consider adding more specific logging in the executeCommandHook method to help with debugging when hooks fail.

✅ Highlights

  • Excellent test coverage with comprehensive integration tests covering various scenarios for each hook type
  • Well-designed architecture with clear separation of concerns between different components
  • Proper error handling that doesn't break the main application flow when hooks fail
  • Good use of TypeScript for type safety throughout the implementation
  • Thoughtful implementation of the permission automation system with blocking and allowing decisions
  • The hook system is designed to be extensible for future hook types

@DennisYu07
Copy link
Copy Markdown
Collaborator Author

Missing early return after cancellation hook (coreToolScheduler.ts:~L1340-1360)

When signal.aborted is true and hooksEnabled && messageBus fires PostToolUseFailureHook, it sets status and returns. But if hooks are not enabled, execution falls through to the original setStatusInternal call below — this is correct but fragile. The two code paths should be unified.

Resolved

@DennisYu07 DennisYu07 temporarily deployed to production-release March 11, 2026 05:52 — with GitHub Actions Inactive
@wenshao
Copy link
Copy Markdown
Collaborator

wenshao commented Mar 11, 2026

ToolErrorType.OUTPUT_TRUNCATED silently replaced with undefined

coreToolScheduler.ts:834: Changed ToolErrorType.OUTPUT_TRUNCATED to undefined — this loses error type information. Why was this changed?

@DennisYu07
Copy link
Copy Markdown
Collaborator Author

Missing return after cancellation causes double-status-set

coreToolScheduler.ts:1329-1353: When signal.aborted and hooks are enabled, the code calls setStatusInternal(callId, 'cancelled', ...) then return. But if hooks are not enabled, it falls through to the existing setStatusInternal call on the next line. The early return only happens inside the if (hooksEnabled && messageBus) block, which is correct — but the control flow is fragile and easy to accidentally break.

Resolved

Copy link
Copy Markdown
Collaborator

@tanzhenxin tanzhenxin left a comment

Choose a reason for hiding this comment

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

LGTM!

@DennisYu07 DennisYu07 merged commit 58bc7a5 into main Mar 19, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants