Skip to content

feat: Hook exec limits#64

Merged
soulteary merged 8 commits intomainfrom
hook-exec-limits
Jan 6, 2026
Merged

feat: Hook exec limits#64
soulteary merged 8 commits intomainfrom
hook-exec-limits

Conversation

@soulteary
Copy link
Copy Markdown
Owner

@soulteary soulteary commented Jan 5, 2026

Note

Adds configurable execution limits and context-aware control for hooks, with corresponding CLI/env options and documentation.

  • New HookExecutor with semaphore-based concurrency control and per-exec timeout; integrated into request flow
  • handleHook now context-aware (exec.CommandContext), with improved temp file cleanup and streaming response tracking
  • New CLI flags/envs: -hook-timeout-seconds, -max-concurrent-hooks, -hook-execution-timeout (and corresponding env vars)
  • Enhanced error handling: proper HTTP statuses for timeouts/cancellations; safer stream writes via tracking response writer
  • Extensive tests added for executor and server paths; minor watcher logging fix; docs (en/zh) updated; .gitignore updated

Written by Cursor Bugbot for commit d3f90f8. This will update automatically on new commits. Configure here.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Comment thread internal/server/server.go
Comment thread internal/server/server.go
Comment thread internal/server/server.go Fixed
Comment thread internal/server/server.go Fixed
Comment thread internal/server/server.go
Comment thread internal/server/server.go Outdated
if errors.Is(err, context.DeadlineExceeded) {
log.Printf("[%s] hook execution timeout: %v", requestID, err)
w.WriteHeader(http.StatusRequestTimeout)
fmt.Fprint(w, "Hook execution timeout. Please check your logs for more details.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Streaming mode WriteHeader called after output written

When StreamCommandOutput is enabled, the command's stdout/stderr are written directly to the ResponseWriter w. If the command produces any output before timing out, w.WriteHeader(http.StatusRequestTimeout) will silently fail because HTTP headers must be sent before body content. The client will receive a 200 OK status (the default when WriteHeader isn't called before the first Write) with partial command output followed by the timeout error message, which is misleading.

Fix in Cursor Fix in Web

Comment thread internal/server/server.go
// 如果写入失败,立即清理这个文件
if removeErr := os.Remove(tmpfile.Name()); removeErr != nil {
log.Printf("[%s] error removing failed temp file %s [%s]", r.ID, tmpfile.Name(), removeErr)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

File handle not closed before removal attempt

When tmpfile.Write() fails, the code attempts to remove the file without first closing the file handle. This causes a file descriptor leak since tmpfile is never closed. Additionally, on Windows, os.Remove() will fail because the file is still open (Windows doesn't allow deleting open files). The tmpfile.Close() call needs to happen before the removal attempt.

Fix in Cursor Fix in Web

Comment thread internal/server/server.go
Comment thread internal/server/server.go
if errors.Is(err, context.DeadlineExceeded) {
log.Printf("[%s] hook execution timeout: %v", requestID, err)
fmt.Fprint(w, "Hook execution timeout. Please check your logs for more details.")
} else if matchedHook.CaptureCommandOutputOnError {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Timeout errors return wrong HTTP status code in capture mode

In CaptureCommandOutput mode, w.WriteHeader(http.StatusInternalServerError) is called unconditionally before checking the error type. This means timeout errors (context.DeadlineExceeded) incorrectly return HTTP 500 instead of HTTP 408 (StatusRequestTimeout). This is inconsistent with the streaming mode which correctly returns 408 for timeouts. The status code determination needs to happen before WriteHeader is called.

Fix in Cursor Fix in Web

@soulteary soulteary merged commit bac48b7 into main Jan 6, 2026
5 checks passed
@soulteary soulteary deleted the hook-exec-limits branch January 6, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants