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
- Inspect the wrapped os error (path + syscall errno) in the message; verify that exact path is creatable as the server user.
- Sanitize/normalize file paths from plan state before joining (reject absolute paths, '..' segments, NUL).
- Ensure the org/temp directory is writable and not deleted during the run (disable aggressive tmp cleaners).
- 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
- Sanitize all plan file paths (reject absolute paths, '..', NUL) before filesystem use.
- Exclude temp-dir trees from aggressive tmpwatch/systemd-tmpfiles cleanup during runs.
- Monitor disk space and permissions on the org data volume.
- Add a pre-flight writability check (create+remove a probe file) before diff computation.
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
- failed to create directory %s: %s
- failed to stat stdin: %v
- error creating convo dir: %v
- error loading accounts: %v
- error setting auth: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/97c6372cd3077992.
Report an issue: GitHub.