{"record":{"id":"b2a557e8f5df04b8","repo":"juicedata/juicefs","slug":"expect-err-is-not-nil","errorCode":null,"errorMessage":"expect err is not nil","messagePattern":"expect err is not nil","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/objbench.go","lineNumber":867,"sourceCode":"\t\t\treturn fmt.Errorf(\"failed to head object %s\", err)\n\t\t} else {\n\t\t\tif h.Key() != key {\n\t\t\t\treturn fmt.Errorf(\"expected key 'test' but got %s\", h.Key())\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\trunCase(\"delete an object\", func(blob object.ObjectStorage) error {\n\t\tbr := []byte(\"hello\")\n\t\tif err := blob.Put(ctx, key, bytes.NewReader(br)); err != nil {\n\t\t\treturn fmt.Errorf(\"put object failed: %s\", err)\n\t\t}\n\t\tif err := blob.Delete(ctx, key); err != nil {\n\t\t\treturn fmt.Errorf(\"delete failed: %s\", err)\n\t\t}\n\t\tif _, err := blob.Head(ctx, key); err == nil {\n\t\t\treturn fmt.Errorf(\"expect err is not nil\")\n\t\t}\n\n\t\tif err := blob.Delete(ctx, key); err != nil {\n\t\t\treturn fmt.Errorf(\"delete not existed: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n\n\trunCase(\"delete non-exist\", func(blob object.ObjectStorage) error {\n\t\tif err := blob.Delete(ctx, key); err != nil {\n\t\t\treturn fmt.Errorf(\"deleting a non-existent object returns an error %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n\n\trunCase(\"list objects\", func(blob object.ObjectStorage) error {\n\t\tbr := []byte(\"hello\")\n\t\tif err := blob.Put(ctx, key, bytes.NewReader(br)); err != nil {","sourceCodeStart":849,"sourceCodeEnd":885,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/cmd/objbench.go#L849-L885","documentation":"This error comes from the 'delete an object' objbench case when blob.Head(ctx, key) unexpectedly SUCCEEDS after the key was deleted. The test expects Head to return an error (object not found) once the object is deleted; a nil error means the delete did not actually take effect or the backend is not reporting absence. objbench throws it to flag a backend that violates the expected delete visibility semantics.","triggerScenarios":"After Put + successful Delete of the test key, blob.Head(ctx, key) returns a nil error and a valid object header — i.e. the object is still listable/headable. Causes include asynchronous delete semantics, a cached/virtual backend (e.g. mem or a buggy filesystem wrapper) that still reports the key, or the delete silently no-oping.","commonSituations":"Testing a custom ObjectStorage implementation that doesn't propagate deletes; a filesystem backend where unlink failed but Delete swallowed the error; eventual-consistency or caching layers (dir lookup cache) serving a stale entry; testing against a bucket with versioning where Head resolves to an old version.","solutions":["Inspect the custom/underlying ObjectStorage implementation's Delete and Head for swallowed errors or stale caching.","Disable or shorten lookup caches (e.g. --attr-cache, dir entry cache) when benchmarking filesystem-backed stores.","Confirm the delete actually hits the backend (list the bucket after the run).","If the backend has asynchronous delete semantics, that backend is non-conformant for JuiceFS; fix the implementation.","Rerun objbench with a clean bucket/prefix to rule out leftover objects with the same key."],"exampleFix":"// before: Head succeeds after Delete\nif _, err := blob.Head(ctx, key); err == nil {\n    return fmt.Errorf(\"expect err is not nil\")\n}\n// after (in the backend implementation): propagate the removal\nfunc (s *store) Delete(ctx context.Context, key string) error {\n    return os.Remove(s.path(key)) // do NOT swallow ENOENT silently\n}","handlingStrategy":"try-catch","validationCode":"// after delete, confirm absence out-of-band before asserting Head fails\n_, err := blob.Head(ctx, key)\ndeleted := err != nil","typeGuard":null,"tryCatchPattern":"if _, err := blob.Head(ctx, key); err == nil {\n    log.Printf(\"object still present after delete; check backend delete semantics/caches\")\n}","preventionTips":["Test custom ObjectStorage implementations against the reference objbench suite before use.","Disable attr/entry caches when benchmarking file-backed stores.","Use versioning-disabled buckets for delete tests.","Clean the test prefix to avoid stale keys."],"tags":["object-storage","head","delete-visibility","benchmark"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}