eyaltoledano/claude-task-master · critical

Failed to restore backup: ${error.message}

Error message

Failed to restore backup: ${error.message}

What it means

restoreBackup() reads a backup file, JSON.parses it into a WorkflowStateBackup, and writes its state back via save(). Any failure at any of those steps — missing backup file, corrupt backup JSON, or a save() failure — is wrapped as 'Failed to restore backup: <cause>'.

Source

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

			if (error.code === 'ENOENT') {
				return [];
			}
			throw new Error(`Failed to list backups: ${error.message}`);
		}
	}

	/**
	 * Restore from a backup
	 */
	async restoreBackup(backupFileName: string): Promise<void> {
		try {
			const backupPath = path.join(this.backupDir, backupFileName);
			const content = await fs.readFile(backupPath, 'utf-8');
			const backup: WorkflowStateBackup = JSON.parse(content);

			await this.save(backup.state);
		} catch (error: any) {
			throw new Error(`Failed to restore backup: ${error.message}`);
		}
	}

	/**
	 * Prune old backups to maintain max backup count
	 */
	private async pruneBackups(): Promise<void> {
		try {
			const backups = await this.listBackups();

			if (backups.length > this.maxBackups) {
				const toDelete = backups.slice(this.maxBackups);

				for (const backup of toDelete) {
					await fs.unlink(path.join(this.backupDir, backup));
				}
			}
		} catch (error: any) {

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Read the cause after the colon: ENOENT means the backup name is wrong; SyntaxError means corrupt JSON; 'Failed to save workflow state' means the write step failed.
  2. Run listBackups() to confirm the exact backup filename exists before restoring.
  3. If JSON is corrupt, choose a different (older) backup file.
  4. Validate the backup's JSON manually (jq <file>) to inspect damage.
  5. Fix filesystem permissions/space if the underlying save() failed.

Example fix

// before
await manager.restoreBackup('state-backup-2026-08-27T10.json'); // guess
// after
const backups = await manager.listBackups();
if (backups.includes('state-backup-2026-08-27T10.json')) {
  await manager.restoreBackup('state-backup-2026-08-27T10.json');
}
Defensive patterns

Strategy: validation

Validate before calling

import { access } from 'fs/promises';
const backupPath = path.join(backupDir, fileName);
await access(backupPath); // throws ENOENT early if backup missing
const content = await readFile(backupPath, 'utf-8');
JSON.parse(content); // throws SyntaxError early if corrupt
if (!('state' in JSON.parse(content))) throw new Error('backup missing state');

Type guard

function isWorkflowStateBackup(v) {
  return v !== null && typeof v === 'object'
    && 'state' in v
    && typeof v.state === 'object'
    && v.state !== null;
}

Try / catch

try {
  await manager.restoreBackup(fileName);
} catch (e) {
  if (/ENOENT/.test(e.message)) {
    const alt = (await manager.listBackups())[0];
    if (alt) await manager.restoreBackup(alt);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling restoreBackup(fileName) with a backup file that does not exist (ENOENT), contains truncated/invalid JSON (SyntaxError from JSON.parse), or whose inner save() fails (disk/permission issues, error 241).

Common situations: Restoring from a backup list captured before a prune removed the file; backup interrupted mid-write leaving truncated JSON; manual edits corrupting the backup; backup produced by an older schema that save() cannot persist.

Related errors


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