juicedata/juicefs · error
first key should be empty string, but got %s
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.
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)
Solutions
- Fix the backend's List to emit the directory entry "" first when listing without a prefix.
- Clear the test directory of extra keys that could alter ordering.
- Compare behavior with the reference file:// implementation in pkg/object and align it.
- Pin/upgrade JuiceFS so the backend and objbench come from the same version.
- 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
- Emit the "" directory entry first in custom List implementations.
- Normalize keys identically in Put and List.
- Use a clean test directory.
- Compare against pkg/object file storage behavior.
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
- list should return 2 keys, but got %d
- first object size should be 0, but got %d
- first key should be test, but got %s
- size of first key shold be 5, but got %v
- list failed: %s
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/33d6b20b621a4942.
Report an issue: GitHub.