plandex-ai/plandex · error

error creating directory: %v

Error message

error creating directory: %v

What it means

Inside the per-path goroutine, os.MkdirAll ensures the subdirectory for a plan file exists in the temp repo before the file body is written. If MkdirAll fails, the error is sent on errCh and the goroutine returns early; GetPlanDiffs wraps it as 'error writing original files to temp dir: error creating directory: ...'.

Source

Thrown at app/server/db/diff_helpers.go:66

	hasAnyOriginal := false

	for path, context := range planState.ContextsByPath {
		go func(path string, context *shared.Context) {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("panic in GetPlanDiffs: %v\n%s", r, debug.Stack())
					errCh <- fmt.Errorf("panic in GetPlanDiffs: %v\n%s", r, debug.Stack())
					runtime.Goexit() // don't allow outer function to continue and double-send to channel
				}
			}()
			_, hasPath := files[path]
			_, hasRemoved := removed[path]
			if hasPath || hasRemoved {
				hasAnyOriginal = true
				// ensure file directory exists
				err = os.MkdirAll(filepath.Dir(filepath.Join(tempDirPath, path)), 0755)
				if err != nil {
					errCh <- fmt.Errorf("error creating directory: %v", err)
					return
				}

				err = os.WriteFile(filepath.Join(tempDirPath, path), []byte(context.Body), 0644)
				if err != nil {
					errCh <- fmt.Errorf("error writing file: %v", err)
					return
				}
			}
			errCh <- nil
		}(path, context)
	}

	for range planState.ContextsByPath {
		err = <-errCh
		if err != nil {
			return "", fmt.Errorf("error writing original files to temp dir: %v", err)
		}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Inspect the wrapped os error (path + syscall errno) in the message; verify that exact path is creatable as the server user.
  2. Sanitize/normalize file paths from plan state before joining (reject absolute paths, '..' segments, NUL).
  3. Ensure the org/temp directory is writable and not deleted during the run (disable aggressive tmp cleaners).
  4. Free disk space / check inode usage if errors are ENOSPC.

Example fix

// before
err = os.MkdirAll(filepath.Dir(filepath.Join(tempDirPath, path)), 0755)
// after (sanitize first)
clean := filepath.Clean(path)
if filepath.IsAbs(clean) || strings.HasPrefix(clean, "..") {
    errCh <- fmt.Errorf("invalid plan path: %s", path)
    return
}
err = os.MkdirAll(filepath.Dir(filepath.Join(tempDirPath, clean)), 0755)
Defensive patterns

Strategy: validation

Validate before calling

func isSafeRelPath(p string) bool {
    if p == "" || filepath.IsAbs(p) || strings.Contains(p, "\x00") {
        return false
    }
    for _, part := range strings.Split(filepath.ToSlash(p), "/") {
        if part == ".." {
            return false
        }
    }
    return true
}

Try / catch

out, err := GetPlanDiffs(orgId, planId, plain)
if err != nil && strings.Contains(err.Error(), "error creating directory") {
    // inspect disk/permissions and retry once after checking temp dir exists
    if _, statErr := os.Stat(getOrgDir(orgId)); statErr != nil {
        log.Printf("org dir gone: %v", statErr)
    }
}

Prevention

When it happens

Trigger: os.MkdirAll(filepath.Dir(filepath.Join(tempDirPath, path))) fails because the temp dir vanished, permissions are wrong, the path component is invalid (e.g. illegal characters on the fs), or disk is full.

Common situations: Temp dir cleaned up concurrently or tmpwatch removing it mid-run; file paths containing invalid segments (leading '/', '..' traversal, NUL bytes) from plan state; read-only org volume; inode/space exhaustion.

Related errors


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