aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-15 23:57:14 -0500
committerCraig Jennings <c@cjennings.net>2026-06-15 23:57:14 -0500
commit546bcf9bbe651eaaa0129346536c9e7e98748555 (patch)
treef152accb4bf9559c78d388665fed7738494783bb
parente749badfd974de42fe8d7cdbe2bfa4fa3e0771b2 (diff)
downloadpearl-546bcf9bbe651eaaa0129346536c9e7e98748555.tar.gz
pearl-546bcf9bbe651eaaa0129346536c9e7e98748555.zip
fix(api): clear auth-source's negative cache on pearl-clear-cache
A key added to authinfo read as missing for up to 2h because auth-source caches the negative lookup and `pearl-clear-cache` only cleared pearl's own caches. It now also calls `auth-source-forget-all-cached`, and the auth-source miss tells the user to run `M-x pearl-clear-cache`. This is the 2026-06-01 live incident, where a freshly-synced Linear key kept erroring as unset.
-rw-r--r--pearl.el11
-rw-r--r--tests/test-pearl-accounts.el9
-rw-r--r--tests/test-pearl-resolve.el9
3 files changed, 25 insertions, 4 deletions
diff --git a/pearl.el b/pearl.el
index 8540b9d..f66490c 100644
--- a/pearl.el
+++ b/pearl.el
@@ -521,7 +521,7 @@ lookup -- when the key cannot be found or SOURCE is malformed."
(secret (and found (plist-get found :secret))))
(cond
((null secret)
- (user-error "No API key for account %s: auth-source found no matching entry"
+ (user-error "No API key for account %s: auth-source found no matching entry (a key added in the last ~2h may be negatively cached; run M-x pearl-clear-cache)"
account))
((functionp secret) (funcall secret))
(t secret))))
@@ -2444,11 +2444,14 @@ none, returns nil. FORCE refreshes the collection cache first."
;;;###autoload
(defun pearl-clear-cache ()
- "Clear the Linear lookup caches (teams, states, per-team collections, views).
-Use after renaming things in Linear, or to force the next lookup to refetch."
+ "Clear the Linear lookup caches and auth-source's credential cache.
+Use after renaming things in Linear, to force the next lookup to refetch, or
+after adding an API key to authinfo so the new key is found without waiting
+out auth-source's cache TTL (default 2h)."
(interactive)
(pearl--clear-account-scoped-state)
- (message "Linear caches cleared"))
+ (auth-source-forget-all-cached)
+ (message "Linear caches cleared (including auth-source)"))
(defun pearl-update-issue-state (issue-id state-name team-id)
"Update the state of Linear issue with ISSUE-ID to STATE-NAME for TEAM-ID."
diff --git a/tests/test-pearl-accounts.el b/tests/test-pearl-accounts.el
index 4de9c2f..82f5e56 100644
--- a/tests/test-pearl-accounts.el
+++ b/tests/test-pearl-accounts.el
@@ -85,6 +85,15 @@
'(:auth-source :host "api.linear.app" :user "work") "work")
:type 'user-error)))
+(ert-deftest test-pearl-resolve-api-key-source-auth-miss-hints-at-cache ()
+ "An auth-source miss hints at the negative cache, so a freshly added key is
+recoverable via clear-cache rather than reading as permanently missing."
+ (cl-letf (((symbol-function 'auth-source-search) (lambda (&rest _) nil)))
+ (let ((err (should-error (pearl--resolve-api-key-source
+ '(:auth-source :host "api.linear.app" :user "work") "work")
+ :type 'user-error)))
+ (should (string-match-p "clear-cache" (error-message-string err))))))
+
;;; pearl--resolve-account
(ert-deftest test-pearl-resolve-account-returns-full-context ()
diff --git a/tests/test-pearl-resolve.el b/tests/test-pearl-resolve.el
index aefe27a..df75b6f 100644
--- a/tests/test-pearl-resolve.el
+++ b/tests/test-pearl-resolve.el
@@ -136,6 +136,15 @@
(should-not pearl--cache-states)
(should-not pearl--cache-teams)))
+(ert-deftest test-pearl-clear-cache-forgets-auth-source ()
+ "Clearing the cache also drops auth-source's own cache, so a key just added
+to authinfo is findable without waiting out the 2h auth-source TTL."
+ (let ((called nil))
+ (cl-letf (((symbol-function 'auth-source-forget-all-cached)
+ (lambda (&rest _) (setq called t))))
+ (pearl-clear-cache)
+ (should called))))
+
;;; failure / malformed cases
(ert-deftest test-pearl-team-collection-malformed-returns-nil-no-cache ()