Skip to content

feat: Expand status callback system into comprehensive hook system for all task lifecycle events #456

@jpoley

Description

@jpoley

GitHub Issue: Add Hook System for Task Lifecycle Events

Repository: MrLesk/Backlog.md
Issue Type: Feature Request
Labels: enhancement, extensibility


Title

feat: Expand status callback system into comprehensive hook system for all task lifecycle events

Summary

Proposal to expand the existing onStatusChange callback mechanism into a comprehensive hook system that supports all task lifecycle events, enabling richer integrations with AI-augmented development workflows and external tooling.

Motivation

Backlog.md already has excellent AI agent integration via MCP and a status change callback system. However, AI-augmented development workflows often need to react to more than just status changes:

Current Capability (via onStatusChange):

  • React to task status transitions
  • Limited to status changes only
  • Single callback per event

Needed Capabilities:

  • Task creation events (initialize resources, capture context)
  • Task updates beyond status (assignee changes, priority shifts)
  • Task archival events (cleanup, metrics collection)
  • Multiple hook scripts per event type

Use Cases:

  1. Task Memory Systems: AI agents need to capture task context when work begins and archive it when complete
  2. Team Notifications: Notify Slack/email on creation, updates, or completion (not just status changes)
  3. External System Integration: Sync with Jira, Linear, GitHub Issues on any task change
  4. Analytics: Track complete task lifecycle (create → update → archive), not just status transitions
  5. Resource Management: Create/cleanup branches, directories, or related artifacts

Related: Issue #392 (Jira sync) would benefit from comprehensive hooks.

Proposed Design

Building on Existing Pattern

The current executeStatusChangeCallback() implementation provides an excellent foundation. The proposal is to generalize this pattern to support:

  1. Additional Hook Events:

    • post-task-create - After task creation
    • post-task-update - After any metadata change (currently status-only)
    • post-task-archive - After task archival
    • Future: pre-task-* events for validation workflows
  2. Hook Discovery:

    • Discover scripts in .backlog/hooks/ directory
    • Scripts named by event: post-task-create.sh, post-task-update.sh, etc.
    • Must be executable (same security model as Git hooks)
  3. Environment Variable Context:

    # All hooks receive:
    BACKLOG_HOOK_EVENT=post-task-create
    BACKLOG_TASK_ID=task-42
    BACKLOG_TASK_TITLE="Implement feature X"
    
    # Event-specific:
    BACKLOG_TASK_STATUS="To Do"                    # create, update
    BACKLOG_OLD_STATUS="To Do"                     # update (when status changes)
    BACKLOG_NEW_STATUS="In Progress"               # update (when status changes)
    BACKLOG_TASK_ASSIGNEE="@user1,@user2"          # create, update
  4. Configuration (extends existing pattern):

    # .backlog/config.yml
    hooks:
      enabled: true                    # Global enable/disable
      directory: .backlog/hooks        # Custom hook directory
      timeout: 5000                    # Timeout in milliseconds
      logLevel: info                   # none, error, info, debug

Implementation Approach

Following the existing executeStatusChangeCallback() pattern:

  1. Create src/core/hooks.ts:

    export enum HookEvent {
      POST_TASK_CREATE = "post-task-create",
      POST_TASK_UPDATE = "post-task-update",
      POST_TASK_ARCHIVE = "post-task-archive",
    }
    
    export async function emitHook(
      event: HookEvent,
      context: HookContext,
      config: BacklogConfig,
      projectRoot: string
    ): Promise<void>
  2. Integration Points (similar to status callback):

    • createTask() - Emit post-task-create after success
    • updateTask() - Emit post-task-update after success
    • archiveTask() - Emit post-task-archive after success
  3. Fail-Safe Execution (same as status callback):

    • Hook failures never block task operations
    • Errors logged but don't affect CLI commands
    • Async execution with configurable timeout

Example Use Cases

Task Memory Integration

#!/bin/bash
# .backlog/hooks/post-task-update.sh

if [[ "$BACKLOG_NEW_STATUS" == "In Progress" ]]; then
    # Capture task context when work begins
    speckit memory capture "$BACKLOG_TASK_ID"
fi

if [[ "$BACKLOG_NEW_STATUS" == "Done" ]]; then
    # Archive context when complete
    speckit memory archive "$BACKLOG_TASK_ID"
fi

Team Notifications

#!/bin/bash
# .backlog/hooks/post-task-create.sh

curl -X POST "$SLACK_WEBHOOK" \
  -d "text=New task created: [$BACKLOG_TASK_ID] $BACKLOG_TASK_TITLE"

Jira Sync (addresses #392)

#!/bin/bash
# .backlog/hooks/post-task-update.sh

if [[ -n "$BACKLOG_NEW_STATUS" ]]; then
    python scripts/sync_to_jira.py \
      --task-id "$BACKLOG_TASK_ID" \
      --status "$BACKLOG_NEW_STATUS"
fi

Design Decisions

Why Expand Rather Than Replace Status Callbacks?

  1. Backward Compatibility: Existing onStatusChange configs continue working
  2. Event Granularity: Not all updates are status changes (assignee, priority, etc.)
  3. Separation of Concerns: Status callbacks for status-specific logic, general hooks for broader automation
  4. Migration Path: Users can migrate incrementally from status callbacks to hooks

Why Script-Based Over Plugin System?

  1. Simplicity: Executable scripts are easier than TypeScript plugins
  2. Consistency: Follows Git hooks, Husky, and other established patterns
  3. Security: No dynamic code loading; explicit script execution
  4. Cross-Language: Hooks can be bash, Python, Node.js, or any executable

Why Post-Hooks Only (Initially)?

Pre-hooks can block operations and add complexity. Starting with post-hooks provides:

  • Event notification without operational risk
  • Simpler implementation and testing
  • Foundation for future pre-hook validation if needed

Backward Compatibility

100% backward compatible

  • Hooks are opt-in (no behavior change without explicit script creation)
  • Existing onStatusChange callbacks continue working
  • No breaking changes to API, CLI, or configuration
  • Default behavior unchanged

Implementation Checklist

I have a complete implementation ready:

  • TypeScript implementation (src/core/hooks.ts)
  • Type definitions (src/types/index.ts additions)
  • Integration points in src/core/backlog.ts
  • Unit tests (Bun test framework)
  • User documentation (docs/hooks.md)
  • Example scripts (task memory, notifications, Jira sync)
  • Configuration schema

Files:

Questions for Maintainer

  1. Interest Level: Is this a feature you'd accept a PR for?
  2. Naming Convention: Prefer post-task-*, on-task-*, or after-task-*?
  3. Relationship to Status Callbacks: Keep both, or deprecate onStatusChange in favor of unified hook system?
  4. Additional Events: Any other lifecycle events you'd like to support?
  5. Documentation Location: Should docs go in docs/ or somewhere else?

Alternative Considered

MCP-Only Integration: Handle all events through MCP tools rather than local hooks.

Rejected Because:

  • MCP is for AI agent queries/actions, not event-driven workflows
  • Local hooks enable non-AI integrations (Slack, Jira, metrics)
  • Hooks complement MCP rather than replace it
  • Scripts provide simpler automation for common tasks

Next Steps

If this aligns with your vision for Backlog.md, I'm happy to:

  1. Submit a PR with the implementation
  2. Iterate on design feedback
  3. Add additional hook events based on your preferences
  4. Update documentation as needed

Looking forward to your thoughts!


References:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions