juicedata/juicefs · error

first key should be empty string, but got

Error message

first key should be empty string, but got %s

What it means

Raised in the objbench 'list objects' filesystem case: the first entry returned by the list must be the directory placeholder with an empty-string key, but a non-empty key was returned. Filesystem-style ObjectStorage implementations are required to include the "" entry representing the listed directory itself.

Solutions

  1. Fix the backend's List to emit the directory entry "" first when listing without a prefix.
  2. Clear the test directory of extra keys that could alter ordering.
  3. Compare behavior with the reference file:// implementation in pkg/object and align it.
  4. Pin/upgrade JuiceFS so the backend and objbench come from the same version.
  5. Rerun objbench against a clean directory.

Example fix

// before
return sortedEntries, nil // "" entry may be missing or misordered
// after
entries := append([]Object{&entry{key: "", size: 0}}, sortedEntries...)
return entries, nil
Defensive patterns

Strategy: type-guard

Validate before calling

objs, _ := listAll(ctx, blob, "", "", 2)
if len(objs) > 0 && objs[0].Key() != "" {
    log.Printf("first entry is not the directory placeholder: %q", objs[0].Key())
}

Type guard

func isFirstDirEntry(objs []object.Object) bool {
    return len(objs) > 0 && objs[0].Key() == ""
}

Try / catch

objs, err := listAll(ctx, blob, "", "", 2)
if err == nil && len(objs) > 0 && objs[0].Key() != "" {
    // handle non-conformant backend: normalize or reject
}

Prevention

When it happens

Trigger: listAll(ctx, blob, "", "", 2) returned 2 objects, but objs[0].Key() != "" — i.e. the backend ordered entries differently or did not synthesize the directory entry at position 0.

Common situations: A custom file backend that sorts entries lexicographically and puts a real object before the "" entry; a backend that never emits the directory placeholder; testing with leftover keys that sort before "test".

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/33d6b20b621a4942. Report an issue: GitHub.

Appendix: source

Thrown at cmd/objbench.go:896

			return fmt.Errorf("deleting a non-existent object returns an error %v", err)
		}
		return nil
	})

	runCase("list objects", func(blob object.ObjectStorage) error {
		br := []byte("hello")
		if err := blob.Put(ctx, key, bytes.NewReader(br)); err != nil {
			return fmt.Errorf("put object failed: %s", err)
		}
		defer blob.Delete(ctx, key) //nolint:errcheck
		if isFileSystem {
			objs, err := listAll(ctx, blob, "", "", 2)
			if err == nil {
				if len(objs) != 2 {
					return fmt.Errorf("list should return 2 keys, but got %d", len(objs))
				}
				if objs[0].Key() != "" {
					return fmt.Errorf("first key should be empty string, but got %s", objs[0].Key())
				}
				if objs[0].Size() != 0 {
					return fmt.Errorf("first object size should be 0, but got %d", objs[0].Size())
				}
				if objs[1].Key() != key {
					return fmt.Errorf("first key should be test, but got %s", objs[1].Key())
				}
				if objs[1].Size() != 5 {
					return fmt.Errorf("size of first key shold be 5, but got %v", objs[1].Size())
				}
				now := time.Now()
				if objs[1].Mtime().Before(now.Add(-30*time.Second)) || objs[1].Mtime().After(now.Add(time.Second*30)) {
					return fmt.Errorf("mtime of key should be within 30 seconds, but got %s", objs[1].Mtime().Sub(now))
				}
			} else {
				return fmt.Errorf("list failed: %s", err)
			}

View on GitHub (pinned to c9a67b23e8)