charmbracelet/crush · error

failed to create parent directories: %w

Error message

failed to create parent directories: %w

What it means

This error wraps an os.MkdirAll failure when MultiEdit creates a brand-new file and its parent directories don't yet exist. The tool needs the target directory present before writing; if mkdir fails (permissions, read-only fs, invalid path, path is a file), the whole new-file creation aborts with this wrapped error. It only fires on the new-file path of processMultiEditWithCreation.

Source

Thrown at internal/agent/tools/multiedit.go:165

func processMultiEditWithCreation(edit editContext, params MultiEditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
	// First edit creates the file
	firstEdit := params.Edits[0]
	if firstEdit.OldString != "" {
		return fantasy.NewTextErrorResponse("first edit must have empty old_string for file creation"), nil
	}

	// Check if file already exists
	if _, err := os.Stat(params.FilePath); err == nil {
		return fantasy.NewTextErrorResponse(fmt.Sprintf("file already exists: %s", params.FilePath)), nil
	} else if !os.IsNotExist(err) {
		return fantasy.ToolResponse{}, fmt.Errorf("failed to access file: %w", err)
	}

	// Create parent directories
	dir := filepath.Dir(params.FilePath)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fantasy.ToolResponse{}, fmt.Errorf("failed to create parent directories: %w", err)
	}

	currentContent, failedEdits, whitespaceCorrected := applyEditsToContent(firstEdit.NewString, params.Edits[1:], 1)

	// Get session and message IDs
	sessionID := GetSessionFromContext(edit.ctx)
	if sessionID == "" {
		return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
	}

	// Check permissions
	_, additions, removals := diff.GenerateDiff("", currentContent, strings.TrimPrefix(params.FilePath, edit.workingDir))

	editsApplied := len(params.Edits) - len(failedEdits)
	var description string
	if len(failedEdits) > 0 {
		description = fmt.Sprintf("Create file %s with %d of %d edits (%d failed)", params.FilePath, editsApplied, len(params.Edits), len(failedEdits))
	} else {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check that no path component of the target is an existing regular file; pick a different path or remove the blocking file
  2. Verify the process has write permission on the closest existing ancestor directory (ls -ld)
  3. If the filesystem is read-only, remount rw or run from a writable directory
  4. Fix the underlying OS error surfaced by %w in the message

Example fix

// before
os.WriteFile("src/missing/new.go", data, 0o644) // parent missing or blocked
// after
if err := os.MkdirAll(filepath.Dir("src/missing/new.go"), 0o755); err != nil { return err }
os.WriteFile("src/missing/new.go", data, 0o644)
Defensive patterns

Strategy: validation

Validate before calling

if dir := filepath.Dir(p); dir != "." {
    if st, err := os.Stat(dir); err == nil && !st.IsDir() {
        return fmt.Errorf("%s exists and is not a directory", dir)
    }
}
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { return err }

Try / catch

var pathErr *os.PathError
if errors.As(err, &pathErr) {
    switch errors.Unwrap(err) {
    case syscall.EACCES: /* fix perms */
    case syscall.ENOTDIR: /* fix path */
    }
}

Prevention

When it happens

Trigger: Calling MultiEdit with a file_path whose directory does not exist or cannot be created: os.MkdirAll returns EACCES, ENOTDIR (a path component is a regular file), EROFS, or a name-too-long error.

Common situations: Running crush in a read-only container or project directory; the agent hallucinating a path where a file component exists as a directory (e.g. src/main.go already a file and target src/main.go/new.go); disk-full or SELinux/ACL denial on the working dir.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/6606fe6a02e62e4b. Report an issue: GitHub.