plandex-ai/plandex · error

error writing plan apply file: %v

Error message

error writing plan apply file: %v

What it means

After creating the applies directory, ApplyPlan writes the marshalled PlanApply record with os.WriteFile at 0644. If that write fails, the OS error is wrapped with this message. It means the apply record could not be persisted even though the directory was created.

Source

Thrown at app/server/db/result_helpers.go:757

	planApply.PlanFileResultIds = resultIds
	planApply.ConvoMessageDescriptionIds = descriptionIds
	planApply.ConvoMessageIds = messageIds

	// Store the PlanApply object
	bytes, err := json.MarshalIndent(planApply, "", "  ")
	if err != nil {
		return fmt.Errorf("error marshalling plan apply: %v", err)
	}

	appliesDir := getPlanAppliesDir(orgId, planId)
	err = os.MkdirAll(appliesDir, 0755)
	if err != nil {
		return fmt.Errorf("error creating applies dir: %v", err)
	}

	err = os.WriteFile(filepath.Join(appliesDir, planApply.Id+".json"), bytes, 0644)
	if err != nil {
		return fmt.Errorf("error writing plan apply file: %v", err)
	}

	msg := "✅ Marked pending results as applied"

	currentFiles := currentPlanState.CurrentPlanFiles.Files
	var sortedFiles []string
	for path := range currentFiles {
		sortedFiles = append(sortedFiles, path)
	}
	sort.Strings(sortedFiles)
	for _, path := range sortedFiles {
		msg += fmt.Sprintf("\n • 📄 %s", path)
	}
	msg += "\n" + "✏️  " + params.CommitMsg

	if loadContextRes != nil && !loadContextRes.MaxTokensExceeded {
		msg += "\n\n" + loadContextRes.Msg
	}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Check free disk space and quotas on the volume hosting the applies dir
  2. Confirm the process has write permission on the applies directory (it was just created with 0755 — owner matters)
  3. Verify planApply.Id is a valid non-empty UUID without path separators before writing
  4. Read the wrapped OS error (ENOSPC/EACCES/EISDIR) and address the specific cause

Example fix

// before
err = os.WriteFile(filepath.Join(appliesDir, planApply.Id+".json"), bytes, 0644)
// after: validate id and surface a clearer failure
if planApply.Id == "" || strings.ContainsAny(planApply.Id, "/\\") {
    return fmt.Errorf("invalid plan apply id: %q", planApply.Id)
}
if err := os.WriteFile(filepath.Join(appliesDir, planApply.Id+".json"), bytes, 0644); err != nil {
    return fmt.Errorf("error writing plan apply file: %v", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if planApply.Id == "" || strings.ContainsAny(planApply.Id, "/\\") {
    return fmt.Errorf("invalid plan apply id: %q", planApply.Id)
}
if err := unix.Access(appliesDir, unix.W_OK); err != nil {
    return fmt.Errorf("applies dir not writable: %v", err)
}

Try / catch

err := os.WriteFile(filepath.Join(appliesDir, planApply.Id+".json"), bytes, 0644)
if err != nil {
    var pathErr *os.PathError
    if errors.As(err, &pathErr) && errors.Is(pathErr.Err, syscall.ENOSPC) {
        return fmt.Errorf("disk full while writing plan apply: %v", err)
    }
    return fmt.Errorf("error writing plan apply file: %v", err)
}

Prevention

When it happens

Trigger: os.WriteFile fails inside ApplyPlan — disk full, permission denied on the newly created dir, planApply.Id empty or containing path-unsafe characters, or the target .json file already exists as a directory/read-only file.

Common situations: Disk quota exceeded after a burst of applies; running the app under a different UID than the data dir owner; empty planApply.Id producing a bare ".json" path; AV/backup tooling locking files on network storage.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/26e5cc88d140d07c. Report an issue: GitHub.