Asynchronously wait for process finish in elisp?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In Emacs Lisp, the normal way to react when a process finishes is not to "wait" in the blocking sense. It is to start the process asynchronously and attach a sentinel that runs when the process status changes. That keeps Emacs responsive and lets your follow-up logic run exactly when the process exits.
Start the Process Asynchronously
make-process is the modern flexible API. It launches the external command and returns immediately.
This does not block Emacs. You can keep editing, switching buffers, or running other Lisp code while the process executes.
Use a Sentinel for Completion
A process sentinel is the standard callback for process state changes. It is triggered when the process exits, is signaled, or changes status.
This is the usual answer to "how do I wait asynchronously" in Elisp. You do not pause execution. You register what should happen next.
Capture Output with a Filter or the Process Buffer
If you need the process output before or after completion, either read it from the process buffer or attach a filter.
The filter runs when output arrives. The sentinel runs when the process status changes. These roles are related, but not interchangeable.
If You Truly Need to Wait, Use accept-process-output
Sometimes code structure forces you to stay in one function until the process produces output or exits. In that case, accept-process-output can wait for process activity while still allowing Emacs to handle I/O. This is more cooperative than a busy loop, but it is still a waiting pattern, not a callback-based design.
This can be useful in tightly controlled code, but it is not the best way to keep a design truly asynchronous.
Choose the Right Mental Model
In Elisp, asynchronous process code works best when you think in terms of events:
- start the process now
- attach a filter if you care about streaming output
- attach a sentinel for completion behavior
- keep follow-up logic inside the callback or in a function called by it
That pattern fits Emacs itself, which is built around event-driven interaction rather than thread-style blocking control flow.
Common Pitfalls
The most common mistake is calling start-process or make-process and then immediately assuming the process is finished. Process startup is asynchronous, so follow-up logic belongs in a sentinel or another explicit coordination mechanism.
Another mistake is putting too much logic directly into the process filter. Filters can be called many times with partial output chunks, so completion logic usually belongs in the sentinel instead.
Developers also sometimes write polling loops without accept-process-output, which wastes CPU and can make the editor feel clumsy. If you must poll, do it cooperatively.
Finally, remember that process exit and successful completion are not the same thing. A sentinel should often inspect the exit status or event text before assuming the external command succeeded.
Summary
- In Elisp, the standard asynchronous completion mechanism is a process sentinel.
- '
make-processstarts the command without blocking Emacs.' - Use filters for streaming output and sentinels for process completion.
- '
accept-process-outputcan cooperatively wait when you truly need a local wait loop.' - Event-driven callbacks are usually cleaner than trying to force synchronous control flow onto asynchronous processes.

