Skip to content

feat: Add user-defined metadata field for arbitrary task properties #1555

@divideby0

Description

@divideby0

Summary

Add an optional metadata: Record<string, unknown> field to tasks and subtasks that allows users to store arbitrary key-value pairs. This field would be preserved across all task operations (moves, updates, expansions, etc.), enabling users to track custom identifiers, external references, and workflow-specific data without requiring schema changes.

Motivation

Currently, if users want to associate custom data with tasks (UUIDs, GitHub issue numbers, external system references, custom workflow states, etc.), they have two problematic options:

  1. Manually edit tasks.json - Works initially, but custom fields are lost when tasks pass through TaskEntity.toJSON() serialization (e.g., during getTaskList())
  2. Request schema changes - Requires PRs and releases for each new field type

This creates friction for common use cases:

  • Linking tasks to GitHub Issues for team coordination
  • Tracking stable UUIDs that survive task renumbering/moving
  • Storing project-specific workflow metadata (sprint IDs, cost centers, etc.)
  • Integrating with external project management tools (Linear, Jira, etc.)

A metadata field solves this by providing a dedicated, schema-stable extension point that Task Master commits to preserving.

Related Issues

Proposed Solution

Schema Change

Add an optional metadata field to the Task and Subtask interfaces:

// packages/tm-core/src/common/types/index.ts

export interface Task extends TaskImplementationMetadata {
  // ... existing fields ...

  /**
   * User-defined metadata that survives all task operations.
   * Use for external IDs, custom workflow data, integrations, etc.
   */
  metadata?: Record<string, unknown>;
}

export interface Subtask extends Omit<Task, 'id' | 'subtasks'> {
  // ... existing fields ...
  // metadata is inherited from Task
}

Implementation Changes

1. Update TaskEntity to preserve metadata

// packages/tm-core/src/modules/tasks/entities/task.entity.ts

export class TaskEntity implements Task {
  // Add property
  metadata?: Record<string, unknown>;

  constructor(data: Task) {
    // ... existing assignments ...
    this.metadata = data.metadata;
  }

  toJSON(): Task {
    return {
      // ... existing fields ...
      metadata: this.metadata,  // Preserve in serialization
    };
  }
}

2. Preserve in storage operations (already works)

The existing FileStorage methods already use spread operators that preserve extra fields:

// FileStorage.normalizeTaskIds - already preserves via ...task spread
// FileStorage.updateTask - already merges via ...tasks[taskIndex] spread

No changes needed here, but tests should verify preservation.

3. Preserve in AI-generated task updates

When AI operations (parse-prd, expand, update-task) generate new task data, existing metadata should be merged:

// When updating a task with AI-generated content
const updatedTask = {
  ...existingTask,           // Preserve existing metadata
  ...aiGeneratedUpdates,     // Apply AI updates
  metadata: {
    ...existingTask.metadata,  // Keep existing metadata
    ...aiGeneratedUpdates.metadata,  // Allow AI to add metadata
  },
};

High-Level Workflow

  1. User manually adds metadata to tasks.json or via future CLI/MCP commands
  2. All task operations (list, update, expand, move) preserve the metadata
  3. External tools can read/write metadata for integrations
  4. AI operations merge (not replace) metadata when updating tasks

Key Elements

Task with metadata example

{
  "id": "1",
  "title": "Implement user authentication",
  "description": "Add OAuth2 login flow",
  "status": "in-progress",
  "priority": "high",
  "dependencies": [],
  "details": "...",
  "testStrategy": "...",
  "subtasks": [],
  "metadata": {
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "githubIssue": 42,
    "githubIssueUrl": "https://github.com/org/repo/issues/42",
    "linearId": "ENG-123",
    "sprint": "2025-Q1-S3",
    "estimatedHours": 8,
    "customWorkflowState": "design-review"
  }
}

CLI enhancements (future, out of initial scope)

# Set metadata via CLI
$ task-master update-task --id=1 --metadata='{"githubIssue": 42}'

# Query tasks by metadata
$ task-master list --filter="metadata.sprint=2025-Q1-S3"

MCP tool enhancements (future, out of initial scope)

// update_task tool accepts metadata parameter
update_task({
  id: "1",
  metadata: { githubIssue: 42 }
})

Example Workflow

# User creates a task
$ task-master add-task --prompt "Implement OAuth login"
→ Created task 15: "Implement OAuth login"

# User manually adds metadata to tasks.json
# (or via future CLI command)
{
  "id": "15",
  "title": "Implement OAuth login",
  "metadata": {
    "uuid": "a1b2c3d4-...",
    "githubIssue": 99
  }
}

# Later, task is expanded with subtasks
$ task-master expand --id=15 --num=3
→ Added 3 subtasks to task 15

# Metadata is preserved!
$ task-master show 15 --json | jq '.metadata'
{
  "uuid": "a1b2c3d4-...",
  "githubIssue": 99
}

# Even if task 15 were renumbered/moved (future feature),
# the UUID allows tracking the same logical task

Implementation Considerations

Backward Compatibility

  • The field is optional; existing tasks.json files work unchanged
  • Reading old files: metadata is undefined, which is valid
  • Writing: only include metadata key if it has content (avoid clutter)

AI Operation Behavior

  • parse-prd: New tasks have no metadata (or allow PRD to define it)
  • expand: Subtasks can inherit parent's metadata or start empty (configurable?)
  • update-task/update-subtask: AI prompts should not overwrite existing metadata unless explicitly instructed

Performance

  • No impact; metadata is just another JSON field
  • No indexing needed for initial implementation

Validation

  • Minimal validation: must be a plain object if present
  • No schema enforcement on metadata contents (that's the point)
  • Consider max size limit to prevent abuse (e.g., 64KB)

Security

  • Metadata is stored in plain text in tasks.json
  • Users should not store secrets (same as other task fields)
  • Document this in usage guidelines

Out of Scope (Future Considerations)

  • CLI commands for metadata (--metadata flag, --filter by metadata)
  • MCP tool parameters for reading/writing metadata
  • Metadata schema validation (optional user-defined schemas)
  • Automatic metadata (auto-generate UUIDs on task creation)
  • Metadata inheritance (subtasks inheriting parent metadata)
  • GitHub/Linear/Jira sync using metadata as the linking mechanism
  • VS Code extension displaying/editing metadata

Testing Requirements

  • Unit tests: TaskEntity preserves metadata through toJSON()
  • Integration tests: metadata survives full task lifecycle (create, update, expand, list)
  • Regression tests: existing tasks without metadata continue to work

Migration

No migration needed - this is purely additive. Existing tasks.json files are compatible.


Checklist for Implementation

  • Add metadata?: Record<string, unknown> to Task interface
  • Add metadata?: Record<string, unknown> to Subtask interface
  • Update TaskEntity constructor to accept metadata
  • Update TaskEntity.toJSON() to include metadata
  • Add unit tests for metadata preservation
  • Add integration tests for full lifecycle
  • Update documentation with metadata usage examples
  • Consider adding to complexity report / task generation prompts (optional)

This issue description was written with assistance from Claude Code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions