Skip to content

Feat/hook manager#1863

Merged
yinwm merged 3 commits intosipeed:refactor/agentfrom
alexhoshina:feat/hook-manager
Mar 22, 2026
Merged

Feat/hook manager#1863
yinwm merged 3 commits intosipeed:refactor/agentfrom
alexhoshina:feat/hook-manager

Conversation

@alexhoshina
Copy link
Collaborator

@alexhoshina alexhoshina commented Mar 21, 2026

📝 Description

🗣️ Type of Change

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 📖 Documentation update
  • ⚡ Code refactoring (no functional changes, no api changes)

🤖 AI Code Generation

  • 🤖 Fully AI-generated (100% AI, 0% Human)
  • 🛠️ Mostly AI-generated (AI draft, Human verified/modified)
  • 👨‍💻 Mostly Human-written (Human lead, AI assisted or none)

🔗 Related Issue

📚 Technical Context (Skip for Docs)

  • Reference URL:
  • Reasoning:

🧪 Test Environment

  • Hardware:
  • OS:
  • Model/Provider:
  • Channels:

📸 Evidence (Optional)

Click to view Logs/Screenshots

☑️ Checklist

  • My code/docs follow the style of this project.
  • I have performed a self-review of my own changes.
  • I have updated the documentation accordingly.

@alexhoshina alexhoshina requested a review from yinwm March 21, 2026 15:25
@sipeed-bot sipeed-bot bot added type: enhancement New feature or request domain: agent domain: config go Pull requests that update go code labels Mar 21, 2026
Comment on lines +379 to +394
done := make(chan error, 1)
go func() {
_, writeErr := ph.stdin.Write(body)
done <- writeErr
}()

select {
case err := <-done:
if err != nil {
return fmt.Errorf("write process hook %q message: %w", ph.name, err)
}
return nil
case <-ctx.Done():
return ctx.Err()
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If ctx.Done() fires, the function returns and defer ph.writeMu.Unlock() is executed. The background goroutine is still blocked trying to write to ph.stdin. When a subsequent hook request arrives, it acquires writeMu and starts a new write. The original goroutine might unblock simultaneously, resulting in interleaved, corrupted JSON being pushed to the external process's standard input.

If an IPC write times out, the stream state is permanently compromised. You cannot safely recover. You must tear down the process.

case <-ctx.Done():
    // The pipe is hopelessly blocked. We must terminate the hook.
    go ph.Close() 
    return ctx.Err()

Comment on lines +400 to +422
for scanner.Scan() {
var msg processHookRPCMessage
if err := json.Unmarshal(scanner.Bytes(), &msg); err != nil {
logger.WarnCF("hooks", "Failed to decode process hook message", map[string]any{
"hook": ph.name,
"error": err.Error(),
})
continue
}
if msg.ID == 0 {
continue
}
ph.pendingMu.Lock()
respCh, ok := ph.pending[msg.ID]
if ok {
delete(ph.pending, msg.ID)
}
ph.pendingMu.Unlock()
if ok {
respCh <- msg
close(respCh)
}
}
Copy link
Collaborator

@afjcjsbx afjcjsbx Mar 21, 2026

Choose a reason for hiding this comment

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

If the external process returns a JSON payload larger than 1MB (e.g., a response containing large base64 images or massive context arrays), scanner.Scan() will return false, and the loop will simply exit. The ProcessHook instance remains registered, but it is no longer reading responses. All pending and future requests to this hook will hang until they hit their context timeouts.

check scanner.Err() and proactively tear down the hook so the system knows it has failed.

for scanner.Scan() { ... }

if err := scanner.Err(); err != nil {
    logger.ErrorCF("hooks", "Process hook read loop failed", map[string]any{"error": err})
    ph.failPending(err)
    go ph.Close()
}

Copy link
Collaborator

@afjcjsbx afjcjsbx left a comment

Choose a reason for hiding this comment

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

only two hints, but nice PR introducing the hooks!

Copy link
Collaborator

@yinwm yinwm left a comment

Choose a reason for hiding this comment

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

LGTM with one follow-up item.

Review Summary:

  • ✅ Well-designed hook interfaces (EventObserver, LLMInterceptor, ToolInterceptor, ToolApprover)
  • ✅ Comprehensive documentation in both English and Chinese
  • ✅ Good test coverage (hooks_test.go, hook_mount_test.go, hook_process_test.go)
  • ✅ Thread-safe implementation with proper mutex usage
  • ✅ JSON-RPC 2.0 for process hooks - standard and extensible
  • ✅ Graceful shutdown with closeOnce pattern

Follow-up item (non-blocking):

  • pkg/agent/hook_process.go: The stderr reader goroutine has no buffer limit. A misbehaving external hook process could exhaust memory by writing infinite stderr. Recommend adding a byte limit (e.g., 64KB) with truncation warning in a follow-up PR.

This is the key piece for Agent Refactor (#1216/#1316) — enables tool approval, LLM interception, and observability. Ready to merge.

@yinwm yinwm merged commit 0432fac into sipeed:refactor/agent Mar 22, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: agent domain: config go Pull requests that update go code type: enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants