eyaltoledano/claude-task-master · error · TaskMasterError

VALIDATION_ERROR

VALIDATION_ERROR

Error message

Tag ${tagName} already exists

What it means

createTag refuses to create a tag whose name is already a key in the legacy-format tasks.json. It throws a TaskMasterError with code VALIDATION_ERROR so callers can distinguish it from generic failures. Tags are unique namespaces for task sets, so duplicate creation is treated as a validation problem, not a filesystem problem.

Source

Thrown at packages/tm-core/src/modules/storage/adapters/file-storage/file-storage.ts:632

	/**
	 * Create a new tag in the tasks.json file
	 * Uses modifyJson for atomic read-modify-write to prevent lost updates
	 */
	async createTag(
		tagName: string,
		options?: { copyFrom?: string; description?: string }
	): Promise<void> {
		const filePath = this.pathResolver.getTasksPath();

		try {
			await this.fileOps.modifyJson(filePath, (existingData: any) => {
				const format = this.formatHandler.detectFormat(existingData);

				if (format === 'legacy') {
					// Legacy format - add new tag key
					if (tagName in existingData) {
						throw new TaskMasterError(
							`Tag ${tagName} already exists`,
							ERROR_CODES.VALIDATION_ERROR
						);
					}

					// Get tasks to copy if specified
					let tasksToCopy: any[] = [];
					if (options?.copyFrom) {
						if (
							options.copyFrom in existingData &&
							existingData[options.copyFrom].tasks
						) {
							tasksToCopy = JSON.parse(
								JSON.stringify(existingData[options.copyFrom].tasks)
							);
						}
					}

View on GitHub (pinned to c0c98d367c)

Solutions

  1. List existing tags first (storage.getTagsWithStats()) and skip creation if the tag already exists.
  2. Catch the TaskMasterError and check error.code === 'VALIDATION_ERROR' to treat duplicates as a no-op.
  3. Use renameTag if the intent was to change an existing tag rather than create a new one.
  4. Use copyTag from an existing tag if you wanted the tasks duplicated under a new name.

Example fix

// before
await storage.createTag('feature-x');
// after
const { tags } = await storage.getTagsWithStats();
if (!tags.some((t) => t.name === 'feature-x')) {
  await storage.createTag('feature-x');
}
Defensive patterns

Strategy: try-catch

Validate before calling

const { tags } = await storage.getTagsWithStats();
if (tags.some((t) => t.name === tagName)) {
  return; // already exists, skip creation
}

Type guard

function tagExists(tagList: Array<{ name: string }>, name: string): tagList is Array<{ name: string }> & { found: true } {
  return tagList.some((t) => t.name === name);
}

Try / catch

import { TaskMasterError, ERROR_CODES } from '@tm/core';
try {
  await storage.createTag(tagName);
} catch (err) {
  if (err instanceof TaskMasterError && err.code === ERROR_CODES.VALIDATION_ERROR) {
    return; // duplicate tag — treat as success
  }
  throw err;
}

Prevention

When it happens

Trigger: storage.createTag('feature-x') when 'feature-x' already exists in tasks.json; re-running an idempotent-looking setup script twice; calling createTag with 'master' when the file already has a master key; a tag created by another process between your existence check and the create.

Common situations: CI pipelines that provision tags on every run without checking existence; team members both creating the same named tag; scripts that assume create is upsert; copy-pasted tag names differing only by case or whitespace confusion.

Related errors


AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29). Data as JSON: /api/errors/fb457c3411e28698. Report an issue: GitHub.