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;
    	}
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. While 'Remove' implies a destructive operation, the description doesn't specify whether this action is reversible, what permissions are required, or how it affects related data (e.g., dependencies). The input schema reveals a 'convert' option that changes the behavior to conversion rather than deletion, but this isn't mentioned in the description, creating a gap in transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence with zero wasted words. It's front-loaded with the core action and target, making it immediately understandable without unnecessary elaboration.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive tool with no annotations and no output schema, the description is inadequate. It lacks critical behavioral details (e.g., reversibility, side effects) and doesn't clarify the 'convert' parameter's impact, leaving the agent with incomplete context for safe and effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all six parameters. The description adds no additional meaning beyond what's in the schema (e.g., it doesn't explain the implications of 'convert' or 'skipGenerate'). With high schema coverage, the baseline score of 3 is appropriate, as the description doesn't compensate but also doesn't detract.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Remove') and the target ('a subtask from its parent task'), which is specific and unambiguous. However, it doesn't differentiate this tool from sibling tools like 'remove_task' or 'clear_subtasks', which handle similar but distinct operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'remove_task' (for standalone tasks) or 'clear_subtasks' (for removing all subtasks). It also doesn't mention prerequisites or contextual constraints, leaving the agent to infer usage from the tool name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

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