aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-25 07:38:30 -0500
committerCraig Jennings <c@cjennings.net>2026-05-25 07:38:30 -0500
commit8ad9fce0f469cecd4d4b682c25272348f35259b2 (patch)
treebd0a8ebde6855a35c9b41c4f1c69943c422ab1e7
parent8bd689aca28bee00bd71e1e7a3149e803fe1049a (diff)
downloadpearl-8ad9fce0f469cecd4d4b682c25272348f35259b2.tar.gz
pearl-8ad9fce0f469cecd4d4b682c25272348f35259b2.zip
fix: guard issue async callbacks against a killed buffer
pearl--push-issue-field (the field setters) and pearl-refresh-current-issue ran goto-char on the captured marker in their success callbacks with no liveness check. If the buffer was killed before the async response returned, the dead marker signaled "Marker does not point anywhere" from the callback. I wrapped the buffer work in a buffer-live-p check; the field setters still report the remote success, and refresh reports that the buffer was closed. This closes the last gap in the command/context test-coverage task. The other seven items it named were already covered by the quick-bug batch's tests; this adds the killed-buffer regression for the issue paths (delete-current-comment already had its own).
-rw-r--r--pearl.el32
-rw-r--r--tests/test-pearl-bugfixes.el25
2 files changed, 45 insertions, 12 deletions
diff --git a/pearl.el b/pearl.el
index 8adca82..8b4ebc7 100644
--- a/pearl.el
+++ b/pearl.el
@@ -2418,13 +2418,18 @@ proceeds and the subtree is replaced with Linear's version."
(message "Issue %s is no longer on Linear (deleted or no access); not refreshing"
issue-id))
(raw
- (save-excursion
- (goto-char marker)
- (pearl--replace-issue-subtree-at-point
- (pearl--normalize-issue raw)))
- (pearl-highlight-comments)
- (pearl--surface-buffer (marker-buffer marker))
- (message "Refreshed %s from Linear" issue-id)))))))))
+ ;; The fetch may complete after the buffer was killed; only
+ ;; rewrite it when it's still live.
+ (if (buffer-live-p (marker-buffer marker))
+ (progn
+ (save-excursion
+ (goto-char marker)
+ (pearl--replace-issue-subtree-at-point
+ (pearl--normalize-issue raw)))
+ (pearl-highlight-comments)
+ (pearl--surface-buffer (marker-buffer marker))
+ (message "Refreshed %s from Linear" issue-id))
+ (message "Refreshed %s; its buffer was closed" issue-id))))))))))
(defun pearl--create-comment-async (issue-id body callback)
"Create a comment with BODY on ISSUE-ID via commentCreate.
@@ -2657,12 +2662,15 @@ tail of the field-setter commands (priority/state/assignee/labels)."
issue-id fields
(lambda (result)
(if (plist-get result :success)
+ ;; The push may complete after the buffer was killed; only touch it
+ ;; when it's still live, but still report the (remote) success.
(progn
- (save-excursion
- (goto-char marker)
- (funcall on-success))
- (message "%s" success-message)
- (pearl--surface-buffer (marker-buffer marker)))
+ (when (buffer-live-p (marker-buffer marker))
+ (save-excursion
+ (goto-char marker)
+ (funcall on-success))
+ (pearl--surface-buffer (marker-buffer marker)))
+ (message "%s" success-message))
(message "%s" fail-message)))))
(defun pearl-set-priority (priority-name)
diff --git a/tests/test-pearl-bugfixes.el b/tests/test-pearl-bugfixes.el
index 7f5c635..caab884 100644
--- a/tests/test-pearl-bugfixes.el
+++ b/tests/test-pearl-bugfixes.el
@@ -151,5 +151,30 @@
(should-error (pearl-run-saved-query "bad") :type 'user-error)
(should-not fetched))))
+;;; Bug: async issue callbacks must be harmless if the buffer is killed
+
+(ert-deftest test-pearl-push-issue-field-killed-buffer-no-signal ()
+ "A field push whose buffer is killed before the success callback does not signal."
+ (let ((buf (generate-new-buffer "pearl-pif-kill")))
+ (unwind-protect
+ (let (marker)
+ (with-current-buffer buf
+ (insert "* h\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nbody\n")
+ (org-mode)
+ (goto-char (point-min))
+ (setq marker (point-marker)))
+ ;; The update stub kills the buffer, then reports success; the success
+ ;; callback must not run its buffer ops (or signal) on a dead buffer.
+ (cl-letf (((symbol-function 'pearl--update-issue-async)
+ (lambda (_id _fields cb) (kill-buffer buf) (funcall cb '(:success t)))))
+ ;; Must complete without signaling; the on-success thunk (which would
+ ;; error) must not run because the buffer is dead.
+ (pearl--push-issue-field
+ "a" marker '(("priority" . 2))
+ (lambda () (error "on-success must not run against a dead buffer"))
+ "ok" "fail")
+ (should-not (buffer-live-p buf))))
+ (when (buffer-live-p buf) (kill-buffer buf)))))
+
(provide 'test-pearl-bugfixes)
;;; test-pearl-bugfixes.el ends here