eyaltoledano/claude-task-master · error · Error
Tag ${tag} not found
Error message
Tag ${tag} not found What it means
deleteTag, operating on a legacy-format tasks.json, deletes the given tag key only if it exists; otherwise it throws this plain Error. Tag names act as top-level keys, and deleting a nonexistent key would silently no-op, so the library makes the absence explicit.
Source
Thrown at packages/tm-core/src/modules/storage/adapters/file-storage/file-storage.ts:730
let shouldDeleteFile = false;
await this.fileOps.modifyJson(filePath, (data: any) => {
if (
this.formatHandler.detectFormat(data) !== 'legacy' &&
tag === 'master'
) {
// Standard format - mark for file deletion after lock release
shouldDeleteFile = true;
return data; // Return unchanged, we'll delete the file after
}
if (this.formatHandler.detectFormat(data) === 'legacy') {
// Legacy format - remove the tag key
if (tag in data) {
delete data[tag];
return data;
} else {
throw new Error(`Tag ${tag} not found`);
}
} else {
throw new Error(`Tag ${tag} not found in standard format`);
}
});
// Delete the file if we're removing master tag from standard format
if (shouldDeleteFile) {
await this.fileOps.deleteFile(filePath);
}
} catch (error: any) {
if (error.code === 'ENOENT') {
throw new Error(`Tag ${tag} not found - file doesn't exist`);
}
throw error;
}
}
View on GitHub (pinned to c0c98d367c)
Solutions
- Enumerate tags first (getTagsWithStats) and only delete when the tag is present.
- Catch the error and treat it as success if your workflow requires idempotent deletion.
- Verify exact tag spelling and case — keys are matched with the 'in' operator, so case matters.
- Confirm the file is in legacy format; in standard format only 'master' can be deleted this way.
Example fix
// before
await storage.deleteTag('old-tag');
// after
const { tags } = await storage.getTagsWithStats();
if (tags.some((t) => t.name === 'old-tag')) {
await storage.deleteTag('old-tag');
} Defensive patterns
Strategy: try-catch
Validate before calling
const { tags } = await storage.getTagsWithStats();
if (!tags.some((t) => t.name === tag)) return; // nothing to delete Type guard
function hasTag(tagList: Array<{ name: string }>, name: string): boolean {
return tagList.some((t) => t.name === name);
} Try / catch
try {
await storage.deleteTag(tag);
} catch (err) {
if (err instanceof Error && err.message === `Tag ${tag} not found`) {
return; // idempotent delete
}
throw err;
} Prevention
- Refresh the tag list immediately before deleting; tags can be removed concurrently.
- Match tag names exactly — key lookup is case-sensitive.
- Make cleanup scripts idempotent so double runs don't fail.
- Confirm the file is in legacy format before deleting non-master tags.
When it happens
Trigger: storage.deleteTag('old-tag') when 'old-tag' is not a key in the legacy tasks.json; deleting a tag already removed by another process; deleting with different casing than the stored key; calling deleteTag for 'master' while the file is in standard format is a different path (see error 216).
Common situations: Cleanup scripts running twice; stale tag lists cached from before a teammate deleted the tag; case-sensitivity mismatches between tag references; tags lost after a manual file edit or merge conflict resolution.
Related errors
- Tag ${oldTag} not found
- Failed to delete file ${filePath}: ${error.message}
- Parent task ${parentId} not found
- Subtask ${subtaskId} not found in parent task ${parentId}
- VALIDATION_ERROR
AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29).
Data as JSON: /api/errors/74a2a0777c3c2be0.
Report an issue: GitHub.