Skip to main content
Glama

remove_subtask

Remove a subtask from its parent task or convert it to a standalone task using the provided subtask ID and project directory. Simplify task management in AI-driven development workflows.

Instructions

Remove a subtask from its parent task

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
convertNoConvert the subtask to a standalone task instead of deleting it
fileNoAbsolute path to the tasks file (default: tasks/tasks.json)
idYesSubtask ID to remove in format 'parentId.subtaskId' (required)
projectRootYesThe directory of the project. Must be an absolute path.
skipGenerateNoSkip regenerating task files
tagNoTag context to operate on

Implementation Reference

  • MCP tool execute handler for 'remove_subtask'. Resolves tag and tasks path, calls removeSubtaskDirect with prepared arguments, handles result and errors.
    execute: withNormalizedProjectRoot(async (args, { log, session }) => {
    	try {
    		const resolvedTag = resolveTag({
    			projectRoot: args.projectRoot,
    			tag: args.tag
    		});
    		log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
    
    		// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
    		let tasksJsonPath;
    		try {
    			tasksJsonPath = findTasksPath(
    				{ projectRoot: args.projectRoot, file: args.file },
    				log
    			);
    		} catch (error) {
    			log.error(`Error finding tasks.json: ${error.message}`);
    			return createErrorResponse(
    				`Failed to find tasks.json: ${error.message}`
    			);
    		}
    
    		const result = await removeSubtaskDirect(
    			{
    				tasksJsonPath: tasksJsonPath,
    				id: args.id,
    				convert: args.convert,
    				skipGenerate: args.skipGenerate,
    				projectRoot: args.projectRoot,
    				tag: resolvedTag
    			},
    			log,
    			{ session }
    		);
    
    		if (result.success) {
    			log.info(`Subtask removed successfully: ${result.data.message}`);
    		} else {
    			log.error(`Failed to remove subtask: ${result.error.message}`);
    		}
    
    		return handleApiResult({
    			result,
    			log: log,
    			errorPrefix: 'Error removing subtask',
    			projectRoot: args.projectRoot
    		});
    	} catch (error) {
    		log.error(`Error in removeSubtask tool: ${error.message}`);
    		return createErrorResponse(error.message);
    	}
    })
  • Zod input schema defining parameters for the remove_subtask tool: id (required), convert, file, skipGenerate, projectRoot, tag.
    parameters: z.object({
    	id: z
    		.string()
    		.describe(
    			"Subtask ID to remove in format 'parentId.subtaskId' (required)"
    		),
    	convert: z
    		.boolean()
    		.optional()
    		.describe(
    			'Convert the subtask to a standalone task instead of deleting it'
    		),
    	file: z
    		.string()
    		.optional()
    		.describe(
    			'Absolute path to the tasks file (default: tasks/tasks.json)'
    		),
    	skipGenerate: z
    		.boolean()
    		.optional()
    		.describe('Skip regenerating task files'),
    	projectRoot: z
    		.string()
    		.describe('The directory of the project. Must be an absolute path.'),
    	tag: z.string().optional().describe('Tag context to operate on')
    }),
  • Tool registry entry that maps 'remove_subtask' to its registration function registerRemoveSubtaskTool.
    import { registerRemoveSubtaskTool } from './remove-subtask.js';
    import { registerRemoveTaskTool } from './remove-task.js';
    import { registerRenameTagTool } from './rename-tag.js';
    import { registerResearchTool } from './research.js';
    import { registerResponseLanguageTool } from './response-language.js';
    import { registerRulesTool } from './rules.js';
    import { registerScopeDownTool } from './scope-down.js';
    import { registerScopeUpTool } from './scope-up.js';
    import { registerUpdateSubtaskTool } from './update-subtask.js';
    import { registerUpdateTaskTool } from './update-task.js';
    import { registerUpdateTool } from './update.js';
    import { registerUseTagTool } from './use-tag.js';
    import { registerValidateDependenciesTool } from './validate-dependencies.js';
    
    // Import TypeScript tools from apps/mcp
    import {
    	registerAutopilotAbortTool,
    	registerAutopilotCommitTool,
    	registerAutopilotCompleteTool,
    	registerAutopilotFinalizeTool,
    	registerAutopilotNextTool,
    	registerAutopilotResumeTool,
    	registerAutopilotStartTool,
    	registerAutopilotStatusTool,
    	registerGenerateTool,
    	registerGetTaskTool,
    	registerGetTasksTool,
    	registerSetTaskStatusTool
    } from '@tm/mcp';
    
    /**
     * Comprehensive tool registry mapping tool names to their registration functions
     * Used for dynamic tool registration and validation
     */
    export const toolRegistry = {
    	initialize_project: registerInitializeProjectTool,
    	models: registerModelsTool,
    	rules: registerRulesTool,
    	parse_prd: registerParsePRDTool,
    	'response-language': registerResponseLanguageTool,
    	analyze_project_complexity: registerAnalyzeProjectComplexityTool,
    	expand_task: registerExpandTaskTool,
    	expand_all: registerExpandAllTool,
    	scope_up_task: registerScopeUpTool,
    	scope_down_task: registerScopeDownTool,
    	get_tasks: registerGetTasksTool,
    	get_task: registerGetTaskTool,
    	next_task: registerNextTaskTool,
    	complexity_report: registerComplexityReportTool,
    	set_task_status: registerSetTaskStatusTool,
    	add_task: registerAddTaskTool,
    	add_subtask: registerAddSubtaskTool,
    	update: registerUpdateTool,
    	update_task: registerUpdateTaskTool,
    	update_subtask: registerUpdateSubtaskTool,
    	remove_task: registerRemoveTaskTool,
    	remove_subtask: registerRemoveSubtaskTool,
  • removeSubtaskDirect helper: validates inputs, enables silent mode, calls core removeSubtask, formats response.
    export async function removeSubtaskDirect(args, log) {
    	// Destructure expected args
    	const { tasksJsonPath, id, convert, skipGenerate, projectRoot, tag } = args;
    	try {
    		// Enable silent mode to prevent console logs from interfering with JSON response
    		enableSilentMode();
    
    		log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
    
    		// Check if tasksJsonPath was provided
    		if (!tasksJsonPath) {
    			log.error('removeSubtaskDirect called without tasksJsonPath');
    			disableSilentMode(); // Disable before returning
    			return {
    				success: false,
    				error: {
    					code: 'MISSING_ARGUMENT',
    					message: 'tasksJsonPath is required'
    				}
    			};
    		}
    
    		if (!id) {
    			disableSilentMode(); // Disable before returning
    			return {
    				success: false,
    				error: {
    					code: 'INPUT_VALIDATION_ERROR',
    					message:
    						'Subtask ID is required and must be in format "parentId.subtaskId"'
    				}
    			};
    		}
    
    		// Validate subtask ID format
    		if (!id.includes('.')) {
    			disableSilentMode(); // Disable before returning
    			return {
    				success: false,
    				error: {
    					code: 'INPUT_VALIDATION_ERROR',
    					message: `Invalid subtask ID format: ${id}. Expected format: "parentId.subtaskId"`
    				}
    			};
    		}
    
    		// Use provided path
    		const tasksPath = tasksJsonPath;
    
    		// Convert convertToTask to a boolean
    		const convertToTask = convert === true;
    
    		// Determine if we should generate files
    		const generateFiles = !skipGenerate;
    
    		log.info(
    			`Removing subtask ${id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
    		);
    
    		// Use the provided tasksPath
    		const result = await removeSubtask(
    			tasksPath,
    			id,
    			convertToTask,
    			generateFiles,
    			{
    				projectRoot,
    				tag
    			}
    		);
    
    		// Restore normal logging
    		disableSilentMode();
    
    		if (convertToTask && result) {
    			// Return info about the converted task
    			return {
    				success: true,
    				data: {
    					message: `Subtask ${id} successfully converted to task #${result.id}`,
    					task: result
    				}
    			};
    		} else {
    			// Return simple success message for deletion
    			return {
    				success: true,
    				data: {
    					message: `Subtask ${id} successfully removed`
    				}
    			};
    		}
    	} catch (error) {
    		// Ensure silent mode is disabled even if an outer error occurs
    		disableSilentMode();
    
    		log.error(`Error in removeSubtaskDirect: ${error.message}`);
    		return {
    			success: false,
    			error: {
    				code: 'CORE_FUNCTION_ERROR',
    				message: error.message
    			}
    		};
    	}
    }
  • Core implementation of removeSubtask: parses ID, finds parent and subtask in tasks.json, removes or converts to new task, writes back to file.
    async function removeSubtask(
    	tasksPath,
    	subtaskId,
    	convertToTask = false,
    	generateFiles = false,
    	context = {}
    ) {
    	const { projectRoot, tag } = context;
    	try {
    		log('info', `Removing subtask ${subtaskId}...`);
    
    		// Read the existing tasks with proper context
    		const data = readJSON(tasksPath, projectRoot, tag);
    		if (!data || !data.tasks) {
    			throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
    		}
    
    		// Parse the subtask ID (format: "parentId.subtaskId")
    		if (!subtaskId.includes('.')) {
    			throw new Error(
    				`Invalid subtask ID format: ${subtaskId}. Expected format: "parentId.subtaskId"`
    			);
    		}
    
    		const [parentIdStr, subtaskIdStr] = subtaskId.split('.');
    		const parentId = parseInt(parentIdStr, 10);
    		const subtaskIdNum = parseInt(subtaskIdStr, 10);
    
    		// Find the parent task
    		const parentTask = data.tasks.find((t) => t.id === parentId);
    		if (!parentTask) {
    			throw new Error(`Parent task with ID ${parentId} not found`);
    		}
    
    		// Check if parent has subtasks
    		if (!parentTask.subtasks || parentTask.subtasks.length === 0) {
    			throw new Error(`Parent task ${parentId} has no subtasks`);
    		}
    
    		// Find the subtask to remove
    		const subtaskIndex = parentTask.subtasks.findIndex(
    			(st) => st.id === subtaskIdNum
    		);
    		if (subtaskIndex === -1) {
    			throw new Error(`Subtask ${subtaskId} not found`);
    		}
    
    		// Get a copy of the subtask before removing it
    		const removedSubtask = { ...parentTask.subtasks[subtaskIndex] };
    
    		// Remove the subtask from the parent
    		parentTask.subtasks.splice(subtaskIndex, 1);
    
    		// If parent has no more subtasks, remove the subtasks array
    		if (parentTask.subtasks.length === 0) {
    			parentTask.subtasks = undefined;
    		}
    
    		let convertedTask = null;
    
    		// Convert the subtask to a standalone task if requested
    		if (convertToTask) {
    			log('info', `Converting subtask ${subtaskId} to a standalone task...`);
    
    			// Find the highest task ID to determine the next ID
    			const highestId = Math.max(...data.tasks.map((t) => t.id));
    			const newTaskId = highestId + 1;
    
    			// Create the new task from the subtask
    			convertedTask = {
    				id: newTaskId,
    				title: removedSubtask.title,
    				description: removedSubtask.description || '',
    				details: removedSubtask.details || '',
    				status: removedSubtask.status || 'pending',
    				dependencies: removedSubtask.dependencies || [],
    				priority: parentTask.priority || 'medium' // Inherit priority from parent
    			};
    
    			// Add the parent task as a dependency if not already present
    			if (!convertedTask.dependencies.includes(parentId)) {
    				convertedTask.dependencies.push(parentId);
    			}
    
    			// Add the converted task to the tasks array
    			data.tasks.push(convertedTask);
    
    			log('info', `Created new task ${newTaskId} from subtask ${subtaskId}`);
    		} else {
    			log('info', `Subtask ${subtaskId} deleted`);
    		}
    
    		// Write the updated tasks back to the file with proper context
    		writeJSON(tasksPath, data, projectRoot, tag);
    
    		// Note: Task file generation is no longer supported and has been removed
    
    		return convertedTask;
    	} catch (error) {
    		log('error', `Error removing subtask: ${error.message}`);
    		throw error;
    	}
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/eyaltoledano/claude-task-master'

If you have feedback or need assistance with the MCP directory API, please join our Discord server