eyaltoledano/claude-task-master · error

Failed to delete workflow state: ${error.message}

Error message

Failed to delete workflow state: ${error.message}

What it means

delete() removes the workflow state file with fs.unlink. A missing file (ENOENT) is treated as success, but any other unlink failure — permissions, directory-is-file mismatch, EBUSY — is wrapped as 'Failed to delete workflow state: <cause>'.

Source

Thrown at packages/tm-core/src/modules/workflow/managers/workflow-state-manager.ts:197

			await fs.writeFile(backupPath, JSON.stringify(backup, null, 2), 'utf-8');

			// Clean up old backups
			await this.pruneBackups();
		} catch (error: any) {
			throw new Error(`Failed to create backup: ${error.message}`);
		}
	}

	/**
	 * Delete workflow state file
	 */
	async delete(): Promise<void> {
		try {
			await fs.unlink(this.statePath);
		} catch (error: any) {
			if (error.code !== 'ENOENT') {
				throw new Error(`Failed to delete workflow state: ${error.message}`);
			}
		}
	}

	/**
	 * List available backups
	 */
	async listBackups(): Promise<string[]> {
		try {
			const files = await fs.readdir(this.backupDir);
			return files
				.filter((f) => f.startsWith('workflow-state-') && f.endsWith('.json'))
				.sort()
				.reverse();
		} catch (error: any) {
			if (error.code === 'ENOENT') {
				return [];
			}

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Read the cause after the colon (EACCES/EPERM/EBUSY etc.).
  2. Check and correct permissions/ownership on the state file and its parent directory.
  3. Close processes holding the file open (editors, sync clients, antivirus on Windows).
  4. If EISDIR, fix the statePath configuration — it must point to a file.
  5. Manually remove the file if the process context cannot (e.g. container user mismatch).

Example fix

// before
await manager.delete(); // EACCES under container user
// after
try {
  await manager.delete();
} catch (e) {
  if (/EACCES|EPERM/.test(e.message)) {
    await exec(`sudo rm -f ${statePath}`); // or fix ownership: chown
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

import { stat, access, constants } from 'fs/promises';
try {
  const s = await stat(statePath);
  if (!s.isFile()) throw new Error('statePath must be a file');
  await access(path.dirname(statePath), constants.W_OK);
} catch { /* missing file = delete is a no-op, fine */ }

Type guard

null

Try / catch

try {
  await manager.delete();
} catch (e) {
  if (/EACCES|EPERM|EBUSY/.test(e.message)) {
    console.error('State file locked or not removable:', e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling delete() when statePath points to a file the process lacks permission to remove, the path is a non-empty directory, or the file is locked/busy (EPERM, EACCES, EBUSY, EISDIR).

Common situations: State file owned by root or another user after sudo runs; read-only mounted volume; Windows file lock from an editor or AV scanner; statePath misconfigured to a directory.

Related errors


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