Skip to content

feat: add user-defined metadata field to tasks (#1555)#1610

Closed
Crunchyman-ralph wants to merge 14 commits intonextfrom
pr-1557
Closed

feat: add user-defined metadata field to tasks (#1555)#1610
Crunchyman-ralph wants to merge 14 commits intonextfrom
pr-1557

Conversation

@Crunchyman-ralph
Copy link
Collaborator

@Crunchyman-ralph Crunchyman-ralph commented Jan 26, 2026

What type of PR is this?

  • 🐛 Bug fix
  • ✨ Feature
  • 🔌 Integration
  • 📝 Docs
  • 🧹 Refactor
  • Other:

Description

Add optional metadata field to tasks and subtasks for storing arbitrary user-defined JSON data. This allows external integrations to link tasks to GitHub issues, Jira tickets, track sprints, story points, and other workflow data without schema changes.

Key features:

  • AI-Safe: Metadata excluded from AI schemas, preserved through all AI operations
  • MCP Support: update_task and update_subtask tools accept a metadata JSON parameter
  • Safety Flag: MCP metadata updates require TASK_MASTER_ALLOW_METADATA_UPDATES=true env var
  • Merge Behavior: New metadata merges with existing (preserves unmodified fields)

Related Issues

Fixes #1555

How to Test This

# Run metadata-specific tests
npm run test -w @tm/core -- --run tests/integration/storage/file-storage-metadata.test.ts tests/integration/ai-operations/metadata-preservation.test.ts tests/integration/mcp-tools/metadata-updates.test.ts

# Manual test: Add metadata to a task in tasks.json
{
  "id": 1,
  "title": "Test task",
  "metadata": {
    "githubIssue": 42,
    "sprint": "Q1-S3"
  }
}

# Verify metadata survives update-task operation
task-master update-task --id=1 --prompt="Add more details"

Expected result:

  • Metadata field persists through all task operations
  • MCP tools reject metadata updates without the safety flag
  • AI operations preserve existing metadata

Contributor Checklist

  • Created changeset: npm run changeset
  • Tests pass: npm test (metadata tests pass; unrelated task-id validation tests have pre-existing failures)
  • Format check passes: npm run format-check (or npm run format to fix)
  • Addressed CodeRabbit comments (if any)
  • Linked related issues (if any)
  • Manually tested the changes

Changelog Entry

Add optional metadata field to tasks for storing user-defined custom data (external IDs, workflow data, integration references)


For Maintainers

  • PR title follows conventional commits
  • Target branch correct
  • Labels added
  • Milestone assigned (if applicable)

Note

Introduces a flexible metadata field on tasks and subtasks, preserved across all AI operations and storage paths, with MCP tooling to safely update it.

  • Core/types/entities: add Task.metadata; ensure serialization preserves it; FileStorage merges/preserves task and subtask metadata during updates (including AI-generated subtasks)
  • MCP tools: update_task/update_subtask accept metadata (JSON string), support metadata-only updates, and require TASK_MASTER_ALLOW_METADATA_UPDATES=true; new validateMcpMetadata utility
  • Direct functions/bridge: plumb metadata through update flows; remote updates forward metadata; improved logging and validation
  • AI schemas: explicitly exclude metadata to prevent overwrites
  • Docs: expand Task Structure with metadata usage, behavior, security note, and MCP examples
  • Tests: add unit/integration suites for metadata preservation, merging, validation, and MCP behavior

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

Summary by CodeRabbit

  • New Features

    • Added optional metadata field for tasks and subtasks to store custom JSON data (external IDs, workflow data, integration data).
    • Metadata persists across all task operations including AI-driven updates.
    • Added MCP tools to update metadata (requires environment flag).
  • Documentation

    • Comprehensive documentation for metadata field, usage scenarios, and examples.
    • Updated task structure documentation with metadata details and MCP guidance.
  • Tests

    • Added integration tests validating metadata preservation across operations.

✏️ Tip: You can customize this high-level summary in your review settings.

divideby0 and others added 14 commits January 2, 2026 17:21
- Add subtask metadata preservation in FileStorage.updateTask
- Add subtask metadata preservation in legacy update-task-by-id.js
- Add test for subtask metadata preservation during AI updates

Fixes Cursor Bugbot review comment about subtask metadata loss
Replace risky position-based fallback with:
- Type-coerced ID comparison (handles string vs number)
- Title-based fallback (subtask titles are typically unique)

This prevents metadata from being assigned to wrong subtask if AI
reorders subtasks or returns IDs with type mismatches.
Add optional `metadata` field to tasks and subtasks for storing
arbitrary user-defined JSON data (external IDs, workflow data,
integration references, etc.).

Key features:
- AI-Safe: Metadata excluded from AI schemas, preserved through all operations
- MCP Support: update_task/update_subtask tools accept metadata parameter
- Safety Flag: MCP updates require TASK_MASTER_ALLOW_METADATA_UPDATES=true
- Metadata Merge: New metadata merges with existing, preserving unmodified fields

Closes #1555

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When both prompt and metadata are provided to update-subtask, the
metadata was being ignored. Now metadata is merged after AI update,
similar to how update-task handles it.

Fixes Cursor Bugbot review comment about ignored metadata.
…servation

Use String(st.id) === String(updatedSubtask.id) instead of strict
equality to handle type mismatches (AI may return string IDs vs numeric).
Also add title-based fallback matching.

Addresses CodeRabbit duplicate comment about FileStorage ID matching.
- Add validateMcpMetadata() utility function in tools/utils.js
- Replace duplicated validation code in update-task.js and update-subtask.js
- Reduces code duplication and ensures consistent validation

Addresses CodeRabbit nitpick about duplicated metadata validation.
Clarifies that these tests focus on validation logic while end-to-end
behavior is covered by FileStorage and AI operation tests.

Addresses CodeRabbit nitpick about test structure.
Changed from replacing metadata to merging it:
- Preserves existing metadata keys from original subtask
- Adds/overrides with new metadata keys from update
- Supports both AI updates (no metadata) and direct updates (with metadata)

Addresses CodeRabbit nitpick about metadata replacement.
Critical fixes for update-subtask-by-id.js:
1. Added 'metadata' to context destructuring (was causing ReferenceError)
2. Updated prompt validation to allow metadata-only updates

Without these fixes, subtask metadata updates would fail at runtime.

Fixes Cursor Bugbot HIGH severity issues.
- Add useResearch and metadata to UpdateBridgeParams interface
- Pass both parameters through to tmCore.tasks.updateWithPrompt
- Update both update-task-by-id.js and update-subtask-by-id.js
- Improve error message in validateMcpMetadata to include parse error

Fixes CodeRabbit outside-diff concern about silently lost parameters.
Addresses CodeRabbit nitpick about error message detail.
- Move validateMcpMetadata to @tm/mcp shared utils (TypeScript)
- Remove duplicate implementation from mcp-server/src/tools/utils.js
- Add comprehensive unit tests for validateMcpMetadata function
- Fix dependencies type in metadata-preservation test (number → string)
- Fix TypeScript union type handling in test assertions
@changeset-bot
Copy link

changeset-bot bot commented Jan 26, 2026

🦋 Changeset detected

Latest commit: 07e3e66

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

This PR adds optional metadata support to tasks and subtasks, enabling storage of arbitrary user-defined JSON data (external IDs, integration info, workflow data). Includes metadata validation, MCP tool integration, storage layer merging logic, and comprehensive test coverage for preservation across AI operations and updates.

Changes

Cohort / File(s) Summary
Documentation & Changeset
.changeset/task-metadata-field.md, apps/docs/capabilities/task-structure.mdx
Introduces comprehensive documentation for the new optional metadata field; covers usage scenarios, behavior across AI operations (parse-prd, update-task, expand, update-subtask), environment flag requirement for MCP updates, and JSON examples.
MCP Validation & Utilities
apps/mcp/src/shared/utils.ts
New validateMcpMetadata() function that validates JSON metadata strings, enforces TASK_MASTER_ALLOW_METADATA_UPDATES environment flag, and returns parsed metadata or structured errors.
MCP Direct Functions
mcp-server/src/core/direct-functions/update-task-by-id.js, mcp-server/src/core/direct-functions/update-subtask-by-id.js
Added optional metadata parameter; updated validation to require either prompt or metadata; metadata passed to core update calls; logging adjusted to reflect metadata-only updates.
MCP Tools
mcp-server/src/tools/update-task.js, mcp-server/src/tools/update-subtask.js
Imported validateMcpMetadata utility; made prompt optional; added metadata parameter (string) with validation; requires TASK_MASTER_ALLOW_METADATA_UPDATES=true for updates; enforces either prompt or metadata provided.
Type Definitions & Entity
packages/tm-core/src/common/types/index.ts, packages/tm-core/src/modules/tasks/entities/task.entity.ts
Added metadata?: Record<string, unknown> field to Task interface and TaskEntity; initialized in constructor; included in toJSON() serialization.
Storage & Bridge Layer
packages/tm-core/src/modules/storage/adapters/file-storage/file-storage.ts, packages/tm-bridge/src/update-bridge.ts
File storage now merges metadata during subtask updates (matching by ID or title, preserving original metadata); extended UpdateBridgeParams with useResearch and metadata fields; propagates both through remote update calls.
Business Logic Update Flows
scripts/modules/task-manager/update-task-by-id.js, scripts/modules/task-manager/update-subtask-by-id.js
Added metadata extraction from context; implemented metadata-only fast path (updates without prompt); metadata merged into tasks after AI updates; metadata propagated through remote bridge calls; subtask metadata preserved during corrections.
Schema Documentation
src/schemas/base-schemas.js
Added documentation note that metadata field is intentionally excluded from AI schemas to prevent overwriting user metadata.
Unit Tests
packages/tm-core/src/modules/tasks/entities/task.entity.spec.ts
Comprehensive test suite validating metadata preservation through TaskEntity constructor, toJSON(), and round-trip serialization; covers edge cases (undefined, null, empty, nested structures, arrays).
Integration Tests
packages/tm-core/tests/integration/ai-operations/metadata-preservation.test.ts, packages/tm-core/tests/integration/mcp-tools/metadata-updates.test.ts, packages/tm-core/tests/integration/storage/file-storage-metadata.test.ts, packages/tm-core/tests/integration/storage/task-metadata-extraction.test.ts
Extensive integration tests validating metadata preservation across AI operations (update, expand, parse-prd), MCP tool metadata validation and merging, file storage round-trips, and JSON serialization; tests environment flag behavior, JSON validation, nested metadata structures, and metadata coexistence with AI fields.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • eyaltoledano
  • maxtuzz
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: add user-defined metadata field to tasks (#1555)' accurately and clearly describes the main change: introducing a new optional metadata field to the task structure.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@Crunchyman-ralph Crunchyman-ralph changed the title style: format metadata test files and update-subtask script feat: add user-defined metadata field to tasks (#1555) #1557 Jan 26, 2026
@Crunchyman-ralph Crunchyman-ralph changed the title feat: add user-defined metadata field to tasks (#1555) #1557 feat: add user-defined metadata field to tasks (#1555) Jan 26, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
mcp-server/src/tools/update-subtask.js (1)

61-66: Sanitize logged arguments to avoid leaking metadata.

Logging full args now includes user-provided metadata (and potentially prompt text). Please log a safe subset (IDs/flags) instead of raw inputs. As per coding guidelines.

🛡️ Proposed fix
-				log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
+				const safeArgs = {
+					id: args.id,
+					tag: resolvedTag,
+					hasPrompt: Boolean(args.prompt?.trim()),
+					hasMetadata: Boolean(args.metadata)
+				};
+				log.info(`Updating subtask with args: ${JSON.stringify(safeArgs)}`);
scripts/modules/task-manager/update-task-by-id.js (1)

501-530: Guard title-based metadata matching to avoid mis-association.

If titles are duplicated, the fallback can attach metadata to the wrong subtask. Consider only using title matches when they’re unique (or skip metadata on ambiguity).

🧩 Safer matching example
-					const originalSubtask = taskToUpdate.subtasks?.find(
-						(st) =>
-							String(st.id) === String(subtask.id) ||
-							(subtask.title && st.title === subtask.title)
-					);
+					const idMatch = taskToUpdate.subtasks?.find(
+						(st) => String(st.id) === String(subtask.id)
+					);
+					const titleMatches = subtask.title
+						? taskToUpdate.subtasks?.filter(
+								(st) => st.title === subtask.title
+						  ) ?? []
+						: [];
+					const originalSubtask =
+						idMatch ?? (titleMatches.length === 1 ? titleMatches[0] : undefined);
🤖 Fix all issues with AI agents
In `@apps/docs/capabilities/task-structure.mdx`:
- Around line 138-147: The "Manual edit" row in the Metadata Behavior table
currently states that you can add/modify metadata directly in tasks.json; change
this to discourage direct edits by rewording the row to something like "Manual
edit (discouraged) — Avoid editing tasks.json directly; use Task Master commands
or MCP (use the metadata parameter) to update metadata" and add a short
parenthetical or footnote pointing readers to Task Master commands and MCP as
the preferred update mechanisms (referencing the terms tasks.json, Task Master,
MCP, and metadata parameter to locate and update the table entry).

In `@packages/tm-core/src/modules/tasks/entities/task.entity.spec.ts`:
- Around line 213-240: The test fails because TaskEntity.toJSON() serializes the
task field as "Details" (capital D) while the Task interface and tests expect
"details" (lowercase); update the TaskEntity.toJSON method in the TaskEntity
class to emit the property name "details" (lowercase) instead of "Details" so
that the output keys match the Task shape and the spec assertions (e.g.,
json.details) pass. Ensure any mapping logic inside TaskEntity.toJSON or its
serializer uses the exact lowercase key and update any related property mappings
in TaskEntity if present.
🧹 Nitpick comments (2)
packages/tm-core/tests/integration/storage/file-storage-metadata.test.ts (1)

51-471: Consider adding legacy/tagged format coverage for metadata preservation.

These tests exercise the default/master format only. Project guidance calls for new features to be tested against both legacy and tagged task data formats, so a small case for a legacy {tasks: [...]} file and a non‑master tag would help. Based on learnings, add coverage for legacy/tagged formats and tag resolution.

packages/tm-core/tests/integration/ai-operations/metadata-preservation.test.ts (1)

62-62: Add tagged/legacy + error-path coverage for metadata preservation.

This suite covers untagged success paths only. Please add a tagged task-list scenario (non-default tag) and verify tag resolution/legacy migration behavior, plus at least one negative case (e.g., invalid metadata payload or missing task id) to cover error handling around metadata updates. Based on learnings and coding guidelines.

Comment on lines +138 to +147
### Metadata Behavior

| Operation | Metadata Behavior |
| ---------------- | ------------------------------------------------------------ |
| `parse-prd` | New tasks are created without metadata |
| `update-task` | Existing metadata is preserved unless explicitly changed |
| `expand` | Parent task metadata is preserved; subtasks don't inherit it |
| `update-subtask` | Subtask metadata is preserved |
| Manual edit | You can add/modify metadata directly in tasks.json |
| MCP (with flag) | Use the `metadata` parameter to explicitly update metadata |
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Manual-edit guidance conflicts with project rules.

The table says metadata can be edited directly in tasks.json, but project guidance discourages manual edits of that file. Consider softening to “discouraged” and point users to Task Master commands/MCP updates instead. Based on learnings, align docs with the “do not manually edit tasks.json” guidance.

📝 Suggested doc tweak
-| Manual edit      | You can add/modify metadata directly in tasks.json           |
+| Manual edit      | Discouraged; prefer Task Master commands or MCP updates      |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Metadata Behavior
| Operation | Metadata Behavior |
| ---------------- | ------------------------------------------------------------ |
| `parse-prd` | New tasks are created without metadata |
| `update-task` | Existing metadata is preserved unless explicitly changed |
| `expand` | Parent task metadata is preserved; subtasks don't inherit it |
| `update-subtask` | Subtask metadata is preserved |
| Manual edit | You can add/modify metadata directly in tasks.json |
| MCP (with flag) | Use the `metadata` parameter to explicitly update metadata |
### Metadata Behavior
| Operation | Metadata Behavior |
| ---------------- | ------------------------------------------------------------ |
| `parse-prd` | New tasks are created without metadata |
| `update-task` | Existing metadata is preserved unless explicitly changed |
| `expand` | Parent task metadata is preserved; subtasks don't inherit it |
| `update-subtask` | Subtask metadata is preserved |
| Manual edit | Discouraged; prefer Task Master commands or MCP updates |
| MCP (with flag) | Use the `metadata` parameter to explicitly update metadata |
🤖 Prompt for AI Agents
In `@apps/docs/capabilities/task-structure.mdx` around lines 138 - 147, The
"Manual edit" row in the Metadata Behavior table currently states that you can
add/modify metadata directly in tasks.json; change this to discourage direct
edits by rewording the row to something like "Manual edit (discouraged) — Avoid
editing tasks.json directly; use Task Master commands or MCP (use the metadata
parameter) to update metadata" and add a short parenthetical or footnote
pointing readers to Task Master commands and MCP as the preferred update
mechanisms (referencing the terms tasks.json, Task Master, MCP, and metadata
parameter to locate and update the table entry).

Comment on lines +213 to +240
it('should preserve all task fields alongside metadata', () => {
const metadata = { custom: 'data' };
const task = createMinimalTask({
id: '42',
title: 'Important Task',
description: 'Do the thing',
status: 'in-progress',
priority: 'high',
dependencies: ['1', '2'],
details: 'Detailed info',
testStrategy: 'Unit tests',
tags: ['urgent'],
metadata
});

const entity = new TaskEntity(task);
const json = entity.toJSON();

expect(json.id).toBe('42');
expect(json.title).toBe('Important Task');
expect(json.description).toBe('Do the thing');
expect(json.status).toBe('in-progress');
expect(json.priority).toBe('high');
expect(json.dependencies).toEqual(['1', '2']);
expect(json.details).toBe('Detailed info');
expect(json.testStrategy).toBe('Unit tests');
expect(json.tags).toEqual(['urgent']);
expect(json.metadata).toEqual(metadata);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

TaskEntity.toJSON() likely emits Details, so this assertion will fail.

The test expects json.details, but packages/tm-core/src/modules/tasks/entities/task.entity.ts currently returns Details (capital D). This will break these assertions and also diverges from the Task interface. Consider aligning the serializer to use details.

🛠️ Suggested fix in packages/tm-core/src/modules/tasks/entities/task.entity.ts
-			Details: this.details,
+			details: this.details,
🤖 Prompt for AI Agents
In `@packages/tm-core/src/modules/tasks/entities/task.entity.spec.ts` around lines
213 - 240, The test fails because TaskEntity.toJSON() serializes the task field
as "Details" (capital D) while the Task interface and tests expect "details"
(lowercase); update the TaskEntity.toJSON method in the TaskEntity class to emit
the property name "details" (lowercase) instead of "Details" so that the output
keys match the Task shape and the spec assertions (e.g., json.details) pass.
Ensure any mapping logic inside TaskEntity.toJSON or its serializer uses the
exact lowercase key and update any related property mappings in TaskEntity if
present.

Copy link

@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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

mode
mode,
useResearch,
...(metadata && { metadata })
Copy link

Choose a reason for hiding this comment

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

Metadata updates silently fail for API storage users

High Severity

The bridge passes metadata to tmCore.tasks.updateWithPrompt(), but the updateTaskWithPrompt interface and API storage implementation don't include metadata in their options type. In api-storage.ts, only { prompt, mode } is sent to the API endpoint, silently ignoring any metadata. API storage users attempting metadata updates via MCP tools will see success messages while their metadata changes are never persisted.

Fix in Cursor Fix in Web

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