Skip to main content
Glama

batch_delete_tasks

Delete multiple tasks filtered by DartQL selector. Preview with dry_run; confirmation required for execution. Tasks go to trash for recovery.

Instructions

[Deprecated: use execute_dartql] Batch delete multiple tasks matching a DartQL selector. MOST DANGEROUS OPERATION! CRITICAL: dry_run defaults to true, confirm=true REQUIRED when dry_run=false. Tasks move to trash (recoverable).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
selectorYesDartQL WHERE clause (SQL-92 syntax). Operators: =, !=, <>, <, >, <=, >=, LIKE, IN, NOT IN, BETWEEN, IS NULL, CONTAINS. Use LIKE with % and _ wildcards for pattern matching.
dry_runNoPreview mode (default: true). Set to false to execute deletions.
confirmNoREQUIRED when dry_run=false. Safety confirmation for deletions.
concurrencyNoParallel deletions (default: 5, range: 1-20)

Implementation Reference

  • The main handler function `handleBatchDeleteTasks` that executes the batch delete logic: validates input, parses DartQL selector, resolves matching tasks, handles dry-run preview, and performs parallel deletions with concurrency control via p-limit.
    export async function handleBatchDeleteTasks(
      input: BatchDeleteTasksInput
    ): Promise<BatchDeleteTasksOutput> {
      const DART_TOKEN = process.env.DART_TOKEN;
    
      if (!DART_TOKEN) {
        throw new DartAPIError(
          'DART_TOKEN environment variable is required. Get your token from: https://app.dartai.com/?settings=account',
          401
        );
      }
    
      // ============================================================================
      // Step 1: Validate input
      // ============================================================================
      if (!input || typeof input !== 'object') {
        throw new ValidationError('input is required and must be an object', 'input');
      }
    
      if (!input.selector || typeof input.selector !== 'string' || input.selector.trim() === '') {
        throw new ValidationError(
          'selector is required and must be a non-empty DartQL WHERE clause (e.g., "status = \'Archived\' AND completed_at < \'2025-01-01\'")',
          'selector'
        );
      }
    
      // ============================================================================
      // Step 2: Validate dry_run and confirm flags (CRITICAL SAFETY)
      // ============================================================================
      // PRODUCTION SAFETY: dry_run defaults to TRUE unless explicitly set to false
      const dryRun = input.dry_run !== false; // Default to true
    
      // CRITICAL: If dry_run=false, confirm MUST be true
      if (!dryRun && input.confirm !== true) {
        throw new ValidationError(
          'SAFETY CHECK FAILED: When dry_run=false, confirm=true is REQUIRED to execute deletions. ' +
            'This prevents accidental batch deletions. Set confirm=true to proceed.',
          'confirm'
        );
      }
    
      // Validate concurrency (default 5, range 1-20)
      let concurrency = input.concurrency ?? 5;
      if (typeof concurrency !== 'number' || !Number.isInteger(concurrency)) {
        throw new ValidationError('concurrency must be an integer', 'concurrency');
      }
      if (concurrency < 1 || concurrency > 20) {
        throw new ValidationError('concurrency must be between 1 and 20', 'concurrency');
      }
    
      // ============================================================================
      // Step 3: Parse DartQL selector
      // ============================================================================
      const parseResult = parseDartQLToAST(input.selector);
    
      if (parseResult.errors.length > 0) {
        throw new ValidationError(
          `DartQL parse errors: ${parseResult.errors.join('; ')}`,
          'selector',
          parseResult.errors
        );
      }
    
      // ============================================================================
      // Step 4: Convert AST to filters
      // ============================================================================
      const filterResult = convertToFilters(parseResult.ast);
    
      if (filterResult.errors.length > 0) {
        throw new ValidationError(
          `DartQL conversion errors: ${filterResult.errors.join('; ')}`,
          'selector',
          filterResult.errors
        );
      }
    
      // ============================================================================
      // Step 5: Resolve selector to dart_ids via list_tasks
      // ============================================================================
      const client = new DartClient({ token: DART_TOKEN });
    
      // Fetch all matching tasks (use high limit to get all)
      let matchingTasks: DartTask[] = [];
      try {
        // Start with API filters if available
        const apiFilters = filterResult.apiFilters;
        let offset = 0;
        const limit = 500;
        let hasMore = true;
    
        while (hasMore) {
          const response = await client.listTasks({
            ...apiFilters,
            limit,
            offset,
          });
    
          matchingTasks.push(...(response.tasks || []));
    
          hasMore = offset + limit < (response.total || 0);
          offset += limit;
    
          // Safety limit: max 10,000 tasks
          if (matchingTasks.length >= 10000) {
            throw new ValidationError(
              'Selector matches too many tasks (>10,000). Please narrow your selector to avoid accidental mass deletion.',
              'selector'
            );
          }
        }
    
        // Apply client-side filtering if needed
        if (filterResult.requiresClientSide && filterResult.clientFilter) {
          matchingTasks = matchingTasks.filter(filterResult.clientFilter);
        }
      } catch (error) {
        if (error instanceof DartAPIError) {
          throw new DartAPIError(
            `Failed to fetch matching tasks: ${error.message}`,
            error.statusCode,
            error.response
          );
        }
        throw error;
      }
    
      const selectorMatched = matchingTasks.length;
    
      // ============================================================================
      // Step 6: Handle dry_run mode (preview only, no deletions)
      // ============================================================================
      if (dryRun) {
        // Preview mode: return max 20 tasks (more than batch_update since deletes are more critical)
        const previewTasks = matchingTasks.slice(0, 20).map((task) => ({
          dart_id: task.dart_id,
          title: task.title,
        }));
    
        return {
          batch_operation_id: 'dry_run',
          selector_matched: selectorMatched,
          dry_run: true,
          preview_tasks: previewTasks,
          successful_deletions: 0,
          failed_deletions: 0,
          deleted_dart_ids: [],
          failed_items: [],
          recoverable: true,
        };
      }
    
      // ============================================================================
      // Step 7: Create batch operation for tracking
      // ============================================================================
      const batchOperation = createBatchOperation('delete', selectorMatched);
      const batchOperationId = batchOperation.batch_operation_id;
      const startTime = Date.now();
    
      // ============================================================================
      // Step 8: Execute parallel deletions with concurrency control
      // ============================================================================
      const limit = pLimit(concurrency);
      const deletedDartIds: string[] = [];
      const failedItems: Array<{ dart_id: string; error: string }> = [];
    
      const deletePromises = matchingTasks.map((task) =>
        limit(async () => {
          try {
            await client.deleteTask(task.dart_id);
    
            deletedDartIds.push(task.dart_id);
            addSuccessfulItem(batchOperationId, task.dart_id);
          } catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
    
            failedItems.push({
              dart_id: task.dart_id,
              error: errorMessage,
            });
    
            addFailedItem(batchOperationId, {
              id: task.dart_id,
              error: errorMessage,
            });
    
            // Continue on error - collect all failures instead of stopping
            // This is safer for batch operations
          }
        })
      );
    
      // Wait for all deletions to complete
      await Promise.all(deletePromises);
    
      // ============================================================================
      // Step 9: Complete batch operation and return results
      // ============================================================================
      const executionTimeMs = Date.now() - startTime;
      const status = failedItems.length === 0 ? 'completed' : failedItems.length === selectorMatched ? 'failed' : 'completed';
      completeBatchOperation(batchOperationId, status);
    
      return {
        batch_operation_id: batchOperationId,
        selector_matched: selectorMatched,
        dry_run: false,
        successful_deletions: deletedDartIds.length,
        failed_deletions: failedItems.length,
        deleted_dart_ids: deletedDartIds,
        failed_items: failedItems,
        recoverable: true,
        execution_time_ms: executionTimeMs,
      };
    }
  • Input type `BatchDeleteTasksInput` with fields: selector (required), dry_run, confirm, concurrency.
    export interface BatchDeleteTasksInput {
      selector: string;
      dry_run?: boolean;
      confirm?: boolean;
      concurrency?: number;
    }
  • Output type `BatchDeleteTasksOutput` with batch_operation_id, selector_matched, dry_run, preview_tasks, successful_deletions, failed_deletions, deleted_dart_ids, failed_items, recoverable, execution_time_ms.
    export interface BatchDeleteTasksOutput {
      batch_operation_id: string;
      selector_matched: number;
      dry_run: boolean;
      preview_tasks?: Array<{ dart_id: string; title: string }>;
      successful_deletions: number;
      failed_deletions: number;
      deleted_dart_ids: string[];
      failed_items: Array<{ dart_id: string; error: string }>;
      recoverable: boolean;
      execution_time_ms?: number;
    }
  • src/index.ts:59-65 (registration)
    Import of `handleBatchDeleteTasks` from the handler module.
    import { handleBatchDeleteTasks } from './tools/batch_delete_tasks.js';
    import { handleExecuteDartQL } from './tools/execute_dartql.js';
    import { handleGetBatchStatus } from './tools/get_batch_status.js';
    import { handleImportTasksCSV } from './tools/import_tasks_csv.js';
    import { handleListDocs } from './tools/list_docs.js';
    import { handleCreateDoc } from './tools/create_doc.js';
    import { handleGetDoc } from './tools/get_doc.js';
  • src/index.ts:560-585 (registration)
    Tool registration entry in the tools list: name 'batch_delete_tasks', description, and inputSchema with selector (required), dry_run, confirm, concurrency.
    {
      name: 'batch_delete_tasks',
      description: '[Deprecated: use execute_dartql] Batch delete multiple tasks matching a DartQL selector. MOST DANGEROUS OPERATION! CRITICAL: dry_run defaults to true, confirm=true REQUIRED when dry_run=false. Tasks move to trash (recoverable).',
      inputSchema: {
        type: 'object',
        properties: {
          selector: {
            type: 'string',
            description: 'DartQL WHERE clause (SQL-92 syntax). Operators: =, !=, <>, <, >, <=, >=, LIKE, IN, NOT IN, BETWEEN, IS NULL, CONTAINS. Use LIKE with % and _ wildcards for pattern matching.',
          },
          dry_run: {
            type: 'boolean',
            description: 'Preview mode (default: true). Set to false to execute deletions.',
          },
          confirm: {
            type: 'boolean',
            description: 'REQUIRED when dry_run=false. Safety confirmation for deletions.',
          },
          concurrency: {
            type: 'integer',
            description: 'Parallel deletions (default: 5, range: 1-20)',
          },
        },
        required: ['selector'],
      },
    },
  • src/index.ts:1064-1074 (registration)
    Case handler in the main request dispatcher that calls `handleBatchDeleteTasks` and returns the JSON-stringified result.
    case 'batch_delete_tasks': {
      const result = await handleBatchDeleteTasks((args || {}) as any);
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    }
Behavior4/5

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

Discloses that the operation is 'MOST DANGEROUS' and that tasks move to trash (recoverable). Without annotations, the description provides key behavioral context, though it could detail concurrency or rate limits.

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?

Extremely concise single sentence with front-loaded deprecation warning. Every phrase adds value (danger, defaults, safety, recovery), no wasted words.

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

Completeness4/5

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

For a dangerous batch operation with no output schema, the description covers purpose, safety, deprecation, and recovery. Lacks details on concurrency or return values, but essential context is present.

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

Parameters4/5

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

Input schema covers all 4 parameters with descriptions (100% coverage). The description adds critical usage semantics: dry_run defaults to true and confirm is required when dry_run=false, going beyond schema.

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

Purpose5/5

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

The description states 'Batch delete multiple tasks matching a DartQL selector' and notes deprecation with alternative 'execute_dartql', clearly distinguishing from sibling tools like 'delete_task' (single) and 'batch_update_tasks'.

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

Usage Guidelines5/5

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

Explicitly mentions deprecation and provides alternative ('use execute_dartql'). Also gives critical safety instructions: dry_run defaults to true and confirm=true required when dry_run=false, guiding correct usage.

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

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/standardbeagle/dart-query'

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