eyaltoledano/claude-task-master · critical

Failed to save workflow state

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).

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.

Appendix: 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)