Summary
The Linux Secret Service backend's Filter is the only platform that treats "no matching secrets" as an error (store.ErrCredentialNotFound). macOS and Windows return an empty (non-nil) map with a nil error for the same situation. This per-OS divergence in a shared interface method appears unnecessary and forces callers to special-case Linux.
Where
store/keychain/keychain_linux.go, Filter — empty collection:
if len(itemPaths) == 0 {
return nil, store.ErrCredentialNotFound
}
...and again at the end of the same function when nothing matched the pattern:
if len(credentials) == 0 {
return nil, store.ErrCredentialNotFound
}
Compare to keychain_darwin.go Filter, which returns make(map[store.ID]store.Secret) + nil even when empty (no empty-as-error guard). Windows behaves the same as macOS.
The Linux backend itself already acknowledges this asymmetry is undesirable — keychain_linux.go GetAllMetadata deliberately returns an empty map on an empty collection, with the comment:
An empty collection is a valid empty result for "list all", not a miss ... This matches the macOS and Windows backends, which have no empty-as-error guard.
Filter was apparently not given the same treatment.
Why it's a problem
- Semantics mismatch.
Filter is a list/query operation. An empty result set is a normal, successful outcome — not a not-found condition. Get returning ErrCredentialNotFound makes sense; Filter doing so does not.
- Inconsistent interface contract. Callers coding against
store.Store cannot rely on uniform behavior across backends, which defeats the purpose of the abstraction and makes bugs invisible to developers on macOS/Windows.
Expected behavior
Filter should return an empty, non-nil map[store.ID]store.Secret with a nil error when no secrets match, on all platforms — matching the existing behavior of GetAllMetadata and of the macOS/Windows Filter implementations.
Proposed fix
Remove both len(...) == 0 → ErrCredentialNotFound guards from keychain_linux.go Filter and return the (empty) credentials map instead. Update/relax any Linux Filter tests that assert ErrCredentialNotFound on empty.
Notes / risk
- This is a behavior change for any Linux caller that currently relies on
Filter returning ErrCredentialNotFound on empty. Such callers should already tolerate an empty map (that's what they get on macOS/Windows), but existing callers should be grepped for errors.Is(err, ErrCredentialNotFound) around Filter before shipping.
- Consider documenting the empty-result contract explicitly on the
Store.Filter interface method to prevent the three implementations from drifting again.
Summary
The Linux Secret Service backend's
Filteris the only platform that treats "no matching secrets" as an error (store.ErrCredentialNotFound). macOS and Windows return an empty (non-nil) map with anilerror for the same situation. This per-OS divergence in a shared interface method appears unnecessary and forces callers to special-case Linux.Where
store/keychain/keychain_linux.go,Filter— empty collection:...and again at the end of the same function when nothing matched the pattern:
Compare to
keychain_darwin.goFilter, which returnsmake(map[store.ID]store.Secret)+nileven when empty (no empty-as-error guard). Windows behaves the same as macOS.The Linux backend itself already acknowledges this asymmetry is undesirable —
keychain_linux.goGetAllMetadatadeliberately returns an empty map on an empty collection, with the comment:Filterwas apparently not given the same treatment.Why it's a problem
Filteris a list/query operation. An empty result set is a normal, successful outcome — not a not-found condition.GetreturningErrCredentialNotFoundmakes sense;Filterdoing so does not.store.Storecannot rely on uniform behavior across backends, which defeats the purpose of the abstraction and makes bugs invisible to developers on macOS/Windows.Expected behavior
Filtershould return an empty, non-nilmap[store.ID]store.Secretwith anilerror when no secrets match, on all platforms — matching the existing behavior ofGetAllMetadataand of the macOS/WindowsFilterimplementations.Proposed fix
Remove both
len(...) == 0 → ErrCredentialNotFoundguards fromkeychain_linux.goFilterand return the (empty)credentialsmap instead. Update/relax any LinuxFiltertests that assertErrCredentialNotFoundon empty.Notes / risk
FilterreturningErrCredentialNotFoundon empty. Such callers should already tolerate an empty map (that's what they get on macOS/Windows), but existing callers should be grepped forerrors.Is(err, ErrCredentialNotFound)aroundFilterbefore shipping.Store.Filterinterface method to prevent the three implementations from drifting again.