Skip to content

Commit 68ccbe6

Browse files
tninjaKang TuCopilot
authored
Fix: guard process filters against deleted buffers on exit, for ghostel and eat terminal (#314)
* fix: guard process filters against deleted buffers on exit When Claude Code exits inside a ghostel or eat terminal buffer, the process filter could fire after the buffer was already killed, causing "error in process filter: Selecting deleted buffer". Add buffer-live-p checks before and after orig-filter in both backends. * test: add dead-buffer regression tests for eat/ghostel filter wrappers Agent-Logs-Url: https://github.com/tninja/ai-code-interface.el/sessions/6ed01d38-1c2e-481d-9dff-dedea927c893 Co-authored-by: tninja <714625+tninja@users.noreply.github.com> * test: polish dead-buffer filter regression assertions Agent-Logs-Url: https://github.com/tninja/ai-code-interface.el/sessions/6ed01d38-1c2e-481d-9dff-dedea927c893 Co-authored-by: tninja <714625+tninja@users.noreply.github.com> * docs: update HISTORY for process-filter dead-buffer fix Agent-Logs-Url: https://github.com/tninja/ai-code-interface.el/sessions/e272f2b3-234d-4703-b24c-10b1013afd57 Co-authored-by: tninja <714625+tninja@users.noreply.github.com> --------- Co-authored-by: Kang Tu <kang_tu@apple.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tninja <714625+tninja@users.noreply.github.com>
1 parent 8db1317 commit 68ccbe6

4 files changed

Lines changed: 138 additions & 15 deletions

File tree

HISTORY.org

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Release history
33

44
** Main branch change
5+
- Fix: Guard eat/ghostel process filters against deleted buffers on exit and add regression tests
56
- Fix: Enable ghostel full-redraw mode for Claude Code and Copilot CLI sessions
67
- Refactor: Use Ghostel public API for sending input
78
- Chore: Notify on response and beep before D-Bus

ai-code-backends-infra-eat.el

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,18 @@ variables for the terminal process."
134134
(set-process-filter
135135
proc
136136
(lambda (process output)
137-
(let ((filtered-output
138-
(with-current-buffer (process-buffer process)
139-
(ai-code-backends-infra--strip-alternate-screen-sequences output))))
140-
(when orig-filter
141-
(funcall orig-filter process filtered-output))
142-
(with-current-buffer (process-buffer process)
143-
(when (ai-code-backends-infra--output-meaningful-p filtered-output)
144-
(ai-code-backends-infra--note-meaningful-output))
145-
(ai-code-session-link--linkify-recent-output filtered-output)))))))
137+
(when-let ((buf (process-buffer process)))
138+
(when (buffer-live-p buf)
139+
(let ((filtered-output
140+
(with-current-buffer buf
141+
(ai-code-backends-infra--strip-alternate-screen-sequences output))))
142+
(when orig-filter
143+
(funcall orig-filter process filtered-output))
144+
(when (buffer-live-p buf)
145+
(with-current-buffer buf
146+
(when (ai-code-backends-infra--output-meaningful-p filtered-output)
147+
(ai-code-backends-infra--note-meaningful-output))
148+
(ai-code-session-link--linkify-recent-output filtered-output)))))))))))
146149
(cons buffer (get-buffer-process buffer)))))
147150

148151
(provide 'ai-code-backends-infra-eat)

ai-code-backends-infra-ghostel.el

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,15 @@ variables for the terminal process."
110110
(set-process-filter
111111
proc
112112
(lambda (process output)
113-
(when orig-filter
114-
(funcall orig-filter process output))
115-
(with-current-buffer (process-buffer process)
116-
(when (ai-code-backends-infra--output-meaningful-p output)
117-
(ai-code-backends-infra--note-meaningful-output))
118-
(ai-code-session-link--linkify-recent-output output))))))
113+
(when-let ((buf (process-buffer process)))
114+
(when (buffer-live-p buf)
115+
(when orig-filter
116+
(funcall orig-filter process output))
117+
(when (buffer-live-p buf)
118+
(with-current-buffer buf
119+
(when (ai-code-backends-infra--output-meaningful-p output)
120+
(ai-code-backends-infra--note-meaningful-output))
121+
(ai-code-session-link--linkify-recent-output output)))))))))
119122
(cons buffer proc)))))
120123

121124
(provide 'ai-code-backends-infra-ghostel)

test/test_ai-code-backends-infra.el

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,64 @@
449449
(when (file-directory-p working-dir)
450450
(delete-directory working-dir t)))))
451451

452+
(ert-deftest test-ai-code-backends-infra-create-terminal-session-eat-filter-ignores-dead-buffer ()
453+
"Eat filter wrapper should skip bookkeeping when session buffer is dead."
454+
(let* ((buffer-name "*test-ai-code-eat-dead-buffer*")
455+
(buffer (get-buffer-create buffer-name))
456+
(ai-code-backends-infra-terminal-backend 'eat)
457+
(wrapped-filter nil)
458+
(orig-filter-called nil)
459+
(strip-called nil)
460+
(note-called nil)
461+
(linkify-called nil))
462+
(unwind-protect
463+
(progn
464+
(cl-letf (((symbol-function 'ai-code-backends-infra--terminal-ensure-backend)
465+
(lambda () nil))
466+
((symbol-function 'eat-mode)
467+
(lambda () nil))
468+
((symbol-function 'eat-exec)
469+
(lambda (&rest _args) nil))
470+
((symbol-function 'get-buffer-process)
471+
(lambda (_buffer) 'eat-proc))
472+
((symbol-function 'process-filter)
473+
(lambda (_process)
474+
(lambda (_process _output)
475+
(setq orig-filter-called t))))
476+
((symbol-function 'set-process-filter)
477+
(lambda (_process filter)
478+
(setq wrapped-filter filter)))
479+
((symbol-function 'process-buffer)
480+
(lambda (_process) buffer))
481+
((symbol-function 'ai-code-backends-infra--strip-alternate-screen-sequences)
482+
(lambda (output)
483+
(setq strip-called t)
484+
output))
485+
((symbol-function 'ai-code-backends-infra--note-meaningful-output)
486+
(lambda (&rest _args)
487+
(setq note-called t)))
488+
((symbol-function 'ai-code-session-link--linkify-recent-output)
489+
(lambda (&rest _args)
490+
(setq linkify-called t))))
491+
(ai-code-backends-infra--create-terminal-session
492+
buffer-name
493+
default-directory
494+
"echo hi"
495+
nil))
496+
(should wrapped-filter)
497+
(kill-buffer buffer)
498+
(should-not
499+
(condition-case nil
500+
(progn (funcall wrapped-filter 'eat-proc "src/foo.el:12\n")
501+
nil)
502+
(error t)))
503+
(should-not orig-filter-called)
504+
(should-not strip-called)
505+
(should-not note-called)
506+
(should-not linkify-called))
507+
(when (buffer-live-p buffer)
508+
(kill-buffer buffer)))))
509+
452510
(ert-deftest test-ai-code-backends-infra-terminal-send-string-delegates-to-vterm-module ()
453511
"Terminal send should delegate vterm specifics to the vterm module."
454512
(let ((buffer (generate-new-buffer " *ai-code-terminal-send-delegate*"))
@@ -745,6 +803,64 @@
745803
(when (buffer-live-p buffer)
746804
(kill-buffer buffer)))))
747805

806+
(ert-deftest test-ai-code-backends-infra-create-terminal-session-ghostel-filter-ignores-dead-buffer ()
807+
"Ghostel filter wrapper should skip bookkeeping when session buffer is dead."
808+
(let* ((buffer-name "*test-ai-code-ghostel-dead-buffer*")
809+
(buffer (get-buffer-create buffer-name))
810+
(orig-filter-called nil)
811+
(note-called nil)
812+
(linkify-called nil)
813+
(wrapped-filter nil)
814+
(ai-code-backends-infra-terminal-backend 'ghostel))
815+
(unwind-protect
816+
(progn
817+
(cl-letf (((symbol-function 'ai-code-backends-infra--terminal-ensure-backend)
818+
(lambda () nil))
819+
((symbol-function 'ghostel-exec)
820+
(lambda (target-buffer _program &optional _args)
821+
(with-current-buffer target-buffer
822+
(setq-local ghostel--process 'ghostel-proc))
823+
'ghostel-proc))
824+
((symbol-function 'get-buffer-process)
825+
(lambda (target-buffer)
826+
(with-current-buffer target-buffer
827+
ghostel--process)))
828+
((symbol-function 'process-filter)
829+
(lambda (_process)
830+
(lambda (_process _output)
831+
(setq orig-filter-called t))))
832+
((symbol-function 'set-process-filter)
833+
(lambda (_process filter)
834+
(setq wrapped-filter filter)))
835+
((symbol-function 'processp)
836+
(lambda (proc)
837+
(eq proc 'ghostel-proc)))
838+
((symbol-function 'process-buffer)
839+
(lambda (_process) buffer))
840+
((symbol-function 'ai-code-backends-infra--note-meaningful-output)
841+
(lambda (&rest _args)
842+
(setq note-called t)))
843+
((symbol-function 'ai-code-session-link--linkify-recent-output)
844+
(lambda (&rest _args)
845+
(setq linkify-called t))))
846+
(ai-code-backends-infra--create-terminal-session
847+
buffer-name
848+
default-directory
849+
"echo hi"
850+
nil))
851+
(should wrapped-filter)
852+
(kill-buffer buffer)
853+
(should-not
854+
(condition-case nil
855+
(progn (funcall wrapped-filter 'ghostel-proc "src/foo.el:12\n")
856+
nil)
857+
(error t)))
858+
(should-not orig-filter-called)
859+
(should-not note-called)
860+
(should-not linkify-called))
861+
(when (buffer-live-p buffer)
862+
(kill-buffer buffer)))))
863+
748864
(ert-deftest test-ai-code-backends-infra-source-comment-uses-repo-stable-rationale ()
749865
"Source comments should avoid local paths and chat transcripts."
750866
(let ((comment-found nil))

0 commit comments

Comments
 (0)