eyaltoledano/claude-task-master · critical

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

Error message

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

What it means

This is the catch-all wrapper around WorkflowStateManager.save(). Any failure during serialization, validation, or the steno atomic file write is rethrown as 'Failed to save workflow state: <cause>'. The original error message is preserved after the colon, so read it to find the root cause (disk full, permissions, invalid JSON).

Source

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

			await this.ensureWriter();

			// Serialize and validate JSON
			const jsonContent = JSON.stringify(state, null, 2);

			// Validate that the JSON is well-formed by parsing it back
			try {
				JSON.parse(jsonContent);
			} catch (parseError) {
				this.logger.error('Generated invalid JSON:', jsonContent);
				throw new Error('Failed to generate valid JSON from workflow state');
			}

			// Write using steno (handles queuing and atomic writes automatically)
			await this.writer!.write(jsonContent + '\n');

			this.logger.debug(`Saved workflow state (${jsonContent.length} bytes)`);
		} catch (error: any) {
			throw new Error(`Failed to save workflow state: ${error.message}`);
		}
	}

	/**
	 * Create a backup of current state
	 */
	async createBackup(): Promise<void> {
		try {
			const exists = await this.exists();
			if (!exists) {
				return;
			}

			const state = await this.load();
			await fs.mkdir(this.backupDir, { recursive: true });

			const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
			const backupPath = path.join(

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Read the cause text after 'Failed to save workflow state:' to identify the underlying error.
  2. Check filesystem permissions and free disk space on the directory containing the state file.
  3. Verify the state object is JSON-serializable (no circular refs/BigInt).
  4. Ensure the state directory exists and the process has write access; create it manually if missing.
  5. Retry if the failure was transient (e.g. temporary EBUSY/EPERM during sync tooling).

Example fix

// before
await manager.save(state); // throws opaque wrapped error
// after
try {
  await manager.save(state);
} catch (e) {
  console.error('root cause:', e.message); // e.g. EACCES: permission denied
  fs.mkdirSync(path.dirname(statePath), { recursive: true });
  await manager.save(state);
}
Defensive patterns

Strategy: try-catch

Validate before calling

import { accessSync, constants, mkdirSync } from 'fs';
const dir = path.dirname(statePath);
try { accessSync(dir, constants.W_OK); } catch { mkdirSync(dir, { recursive: true }); }

Type guard

null

Try / catch

try {
  await manager.save(state);
} catch (e) {
  const cause = e.message.replace('Failed to save workflow state: ', '');
  if (/EACCES|EPERM/.test(cause)) fixPermissions(dir);
  else if (/ENOSPC/.test(cause)) freeDiskSpace();
  else if (/invalid JSON/.test(cause)) sanitizeState(state);
  throw e;
}

Prevention

When it happens

Trigger: Any save() call (also via startWorkflow, resumeWorkflow, restoreBackup) where JSON.stringify/parse fails or this.writer.write() rejects — e.g. the state directory is read-only, disk is full, or steno write throws.

Common situations: Read-only filesystem or wrong permissions on the workflow state path; ENOSPC on a full disk; state file locked by another process; invalid JSON from non-serializable state (error 240's cause wrapped here).

Related errors


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