Remember which keyring backend was found, instead of searching every run - #37
Merged
Merged
Conversation
Left to itself, keyring works out which backend to use by loading every backend that every installed package registers and keeping the best of them. That search runs again on every invocation and is the single most expensive thing a keycmd run does: measured here it is the difference between 0.156s and 0.085s, and it grows with the number of packages installed and with any backend that takes its time deciding it is not viable. The answer, though, is the same every time until the packages on the machine change. So the new backend.py writes it down the first time a run needs a credential and loads that backend by name afterwards, which is the shortcut PYTHON_KEYRING_BACKEND buys without anyone having to know the variable exists. Nothing to read, nothing to set: the second run is simply faster than the first. The note goes where the platform keeps files a program can afford to lose -- %LOCALAPPDATA% on windows, ~/Library/Caches on macOS, $XDG_CACHE_HOME on linux -- and holds one line, and is trusted only as far as it can be checked: - is_backend_name keeps anything that is not a dotted class name from reaching an import, so a file that has been truncated or scribbled in is worth no more than a search - a name that no longer loads sends the run back to searching and is replaced. load_keyring asks the class for its priority on the way, which is how keyring itself decides a backend is viable, so an uninstalled backend and one whose daemon stopped are both caught - backend_name looks through the chainer, which is not a backend but the search wearing one's clothes; writing that down would leave the search in place, so the backend it would have reached first is written instead - PYTHON_KEYRING_BACKEND outranks the note and is never written over - a search that found nothing is not an answer, and is not remembered What keycmd cannot notice by itself is a backend that still loads but is no longer the one you want, so the two steps are also available on purpose: keycmd --detect-backend # search now, and remember what turns up keycmd --reset-backend # forget it, so the next run searches again Two ways of ending up with no backend used to reach the user as a traceback and are now errors that say what to do: keyring settling on fail.Keyring, which raises on the first lookup and which inside a distro points at the WSL section of the README, and a PYTHON_KEYRING_BACKEND that cannot be loaded. logs.error grew the hint lines those need. keyring stays out of the import path of a run that looks up no credential: backend.py reaches for it inside the functions that need it, the way creds.py used to, and its own name only appears at module level under TYPE_CHECKING. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRYugGcVh3wvC5a5pzwKU3
Korijn
force-pushed
the
claude/keycmd-keyring-instructions-dixlfa
branch
from
August 5, 2026 11:08
efe2453 to
728e94e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Left to itself, keyring works out which backend to use by loading every backend that every installed package registers and keeping the best of them. That search runs again on every invocation and is the single most expensive thing a keycmd run does: measured on the machine this was written on it is the difference between 0.156s and 0.085s, and it grows with the number of packages installed and with any backend that takes its time deciding it is not viable.
The answer, though, is the same every time until the packages on the machine change. So
backend.pywrites it down the first time a run needs a credential and loads that backend by name afterwards — the shortcutPYTHON_KEYRING_BACKENDbuys, without anyone having to know the variable exists. Nothing to read, nothing to set; the second run is simply faster than the first.The note, and how far it is trusted
One line, in the folder the platform keeps files a program can afford to lose:
%LOCALAPPDATA%on windows,~/Library/Cacheson macOS,$XDG_CACHE_HOMEon linux. Deleting it costs one slow run. It is treated as untrusted input throughout:is_backend_namekeeps anything that is not a dotted class name from reaching an import, so a file that has been truncated or scribbled in is worth no more than a fresh search.load_keyringasks the class for itspriorityon the way, which is how keyring itself decides a backend is viable, so a backend that was uninstalled and one whose daemon stopped are both caught by the sameexcept.backend_namelooks through the chainer, which is not a backend but the search wearing one's clothes — writing that down would leave the search in place — and names the backend it would have reached first, so the credentials keep coming from where they already did.PYTHON_KEYRING_BACKENDoutranks the note and is never written over.os.replace, so a second keycmd running at the same moment reads either the old name or the new one. A cache folder that cannot be written is a slow run, not a failed one.The two commands
What keycmd cannot notice by itself is a backend that still loads but is no longer the one you want — you installed a better one, or removed a package and want the runner-up. So both steps are available on purpose:
Errors that used to be tracebacks
Two ways of ending up with no backend reached the user as a traceback, and both are now errors that say what to do —
logs.errorgrew the hint lines they need:Inside a distribution that error also points at the WSL section of the README, which is what the situation almost always is. A
PYTHON_KEYRING_BACKENDthat cannot be loaded is reported with the value it was given.Testing
156 passed / 16 skipped with no keyring available, 169 / 3 with one.
tests/test_backend.pycovers the cache paths of all three platforms, the name check, remembering and forgetting, an unwritable cache, a scribbled note, a stale note, and every branch ofload_backend;tests/test_cli.pycovers the two flags and the run that quietly writes the note. Two things worth knowing for future tests, and now in CLAUDE.md: the autousecache_homefixture pointsCACHE_HOMEattmp_pathso a run never touches the note the machine is using, and a test about remembering has todelenvPYTHON_KEYRING_BACKENDfirst, since the README suggests running the suite with it set and it outranks everything here.keyring stays out of the import path of a run that looks up no credential, and
test_cli_import_stays_leanstill passes. The version is untouched, since that moves in its own commits.🤖 Generated with Claude Code
https://claude.ai/code/session_01CRYugGcVh3wvC5a5pzwKU3