Add config-based close hooks (bd-g4b4)
Implements on_close hooks in .beads/config.yaml for automation and notifications. Hooks receive issue data via environment variables (BEAD_ID, BEAD_TITLE, BEAD_TYPE, BEAD_PRIORITY, BEAD_CLOSE_REASON) and run via sh -c. Changes: - internal/config: Add HookEntry type and GetCloseHooks() - internal/hooks: Add RunConfigCloseHooks() for executing config hooks - cmd/bd: Call RunConfigCloseHooks after successful close - docs/CONFIG.md: Document hooks configuration with examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
66
internal/hooks/config_hooks.go
Normal file
66
internal/hooks/config_hooks.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Package hooks provides a hook system for extensibility.
|
||||
// This file implements config-based hooks defined in .beads/config.yaml.
|
||||
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/beads/internal/config"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
// RunConfigCloseHooks executes all on_close hooks from config.yaml.
|
||||
// Hook commands receive issue data via environment variables:
|
||||
// - BEAD_ID: Issue ID (e.g., bd-abc1)
|
||||
// - BEAD_TITLE: Issue title
|
||||
// - BEAD_TYPE: Issue type (task, bug, feature, etc.)
|
||||
// - BEAD_PRIORITY: Priority (0-4)
|
||||
// - BEAD_CLOSE_REASON: Close reason if provided
|
||||
//
|
||||
// Hooks run synchronously but failures are logged as warnings and don't
|
||||
// block the close operation.
|
||||
func RunConfigCloseHooks(ctx context.Context, issue *types.Issue) {
|
||||
hooks := config.GetCloseHooks()
|
||||
if len(hooks) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Build environment variables for hooks
|
||||
env := append(os.Environ(),
|
||||
"BEAD_ID="+issue.ID,
|
||||
"BEAD_TITLE="+issue.Title,
|
||||
"BEAD_TYPE="+string(issue.IssueType),
|
||||
"BEAD_PRIORITY="+strconv.Itoa(issue.Priority),
|
||||
"BEAD_CLOSE_REASON="+issue.CloseReason,
|
||||
)
|
||||
|
||||
timeout := 10 * time.Second
|
||||
|
||||
for _, hook := range hooks {
|
||||
hookCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
|
||||
// #nosec G204 -- command comes from user's config file
|
||||
cmd := exec.CommandContext(hookCtx, "sh", "-c", hook.Command)
|
||||
cmd.Env = env
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
err := cmd.Run()
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
// Log warning but don't fail the close
|
||||
name := hook.Name
|
||||
if name == "" {
|
||||
name = hook.Command
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Warning: close hook %q failed: %v\n", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user