dagger/dagger · error

file name %q must not contain a directory

Error message

file name %q must not contain a directory

What it means

FileBlobLazy.Evaluate validates that the filename given to a blob-backed file (created via an API like File.withNewFile/with blob contents) contains no path separators. A blob file must be a single entry at the snapshot root; passing something like "sub/dir/file.txt" makes the lazy evaluation fail immediately. This is a direct input-validation error raised at evaluation time.

Source

Thrown at core/file.go:501

	case persistedFileLazyKindChown:
		var persisted persistedFileChownLazy
		if err := json.Unmarshal(payload, &persisted); err != nil {
			return nil, fmt.Errorf("decode persisted file chown lazy: %w", err)
		}
		parent, err := loadPersistedObjectResultByResultID[*File](ctx, dag, persisted.ParentResultID, "file chown parent")
		if err != nil {
			return nil, err
		}
		return &FileChownLazy{LazyState: NewLazyState(), Parent: parent, Owner: persisted.Owner}, nil
	default:
		return nil, fmt.Errorf("decode persisted file lazy payload: unsupported lazy kind %q", lazyKind)
	}
}

func (lazy *FileBlobLazy) Evaluate(ctx context.Context, file *File) error {
	return lazy.LazyState.Evaluate(ctx, "File.blob", func(ctx context.Context) error {
		if dir, _ := filepath.Split(lazy.Filename); dir != "" {
			return fmt.Errorf("file name %q must not contain a directory", lazy.Filename)
		}
		if err := ValidateFileName(lazy.Filename); err != nil {
			return err
		}
		permissions := lazy.Permissions
		if permissions == 0 {
			permissions = 0o644
		}

		query, err := CurrentQuery(ctx)
		if err != nil {
			return err
		}
		scratch, err := query.SnapshotManager().Scratch(ctx)
		if err != nil {
			return fmt.Errorf("create blob scratch snapshot: %w", err)
		}
		newRef, err := query.SnapshotManager().New(

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Pass only the base filename, then place the file in a directory using Directory.withNewFile instead of File APIs
  2. Strip or split off the directory component before the call
  3. Create the file via `directory.WithNewDirectory("docs").WithNewFile("readme.md", contents)`
  4. Validate names client-side with ValidateFileName before calling the API

Example fix

// before
file := dir.WithNewFile("docs/readme.md", contents)
// after
dir = dir.WithNewDirectory("docs")
file := dir.WithNewFile("readme.md", contents)
Defensive patterns

Strategy: validation

Validate before calling

func validBlobName(name string) error {
    if dir, _ := filepath.Split(name); dir != "" {
        return fmt.Errorf("name %q must not contain a directory", name)
    }
    return ValidateFileName(name)
}
// call before: validBlobName("readme.md") instead of "docs/readme.md"

Type guard

func isBareFileName(name string) bool {
    dir, _ := filepath.Split(name)
    return dir == "" && name != "" && name != "." && name != ".."
}

Try / catch

f, err := dir.WithNewFile(name, contents)
if err != nil && strings.Contains(err.Error(), "must not contain a directory") {
    dir = dir.WithNewDirectory(filepath.Dir(name))
    f, err = dir.WithNewFile(filepath.Base(name), contents)
}

Prevention

When it happens

Trigger: Creating a new blob file where the name argument contains a directory component (filepath.Split yields a non-empty dir), e.g. `file.withNewFile("docs/readme.md", contents)`.

Common situations: Users assuming withNewFile accepts relative paths inside the container; porting code that built nested paths; generating filenames by joining path fragments; templates that interpolate full paths.

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/decc9ff18531c67a. Report an issue: GitHub.