fix(provider): terminate job before closing terminal to prevent orphaned processes#168
Conversation
9a5837e to
d41da26
Compare
d41da26 to
f56624a
Compare
Can you try again with the latest version? |
f56624a to
733c590
Compare
Track the terminal's PID at toggle/start time and use shell 'kill' to terminate it during VimLeavePre. This is more reliable than jobstop() because snacks.terminal cleanup can invalidate the job before our stop() is called.
733c590 to
ca9251c
Compare
|
I think the root issue is in |
nickjvandyke
left a comment
There was a problem hiding this comment.
Thanks for this clever solution! Just a couple questions 😁
… Windows support Move PID capture logic into an on_buf callback in the constructor so it fires automatically for both toggle() and open(), eliminating duplicated code. Wrap the user's existing on_buf callback to preserve custom behavior (e.g., keymap application). Guard the shell 'kill -TERM' call with a Unix platform check and fall back to vim.uv.kill() on non-Unix systems for cross-platform compatibility.
…l provider Extract process kill and PID capture logic into opencode.provider.util, shared by both snacks and terminal providers. This eliminates duplicated code and ensures consistent behavior. Key fix: use os.execute() instead of vim.fn.system() for process group kill during VimLeavePre, and skip jobstop when PID kill succeeds to prevent SIGHUP from causing the process to respawn. Apply the same PID-based termination to the terminal provider, which suffered from the same orphaned process issue as the snacks provider.
Add diagnostic disable/enable annotations around private field access in the on_buf closure, which is part of the constructor but lua-ls treats as an external context.
…haned processes Add PID-based process group kill to the tmux provider, matching the approach used by the snacks and terminal providers. Capture the pane PID via tmux display-message and kill the process group before tmux kill-pane. This is a workaround for anomalyco/opencode#13001 (opencode does not handle SIGHUP gracefully). Document the upstream issue in provider.util so the workaround is easy to identify and remove later.
nickjvandyke
left a comment
There was a problem hiding this comment.
This is looking great, thank you 😀 just a couple more questions, then I think we are set! Thanks for all the digging you did - turns out there are a lot of quirks here 😅
…ture Remove job_id parameter from util.kill() and the jobstop fallback, since jobstop sends SIGHUP which causes the process to respawn — making it counterproductive. Only PID-based process group kill is effective. Add comments explaining why PID must be captured eagerly at startup: terminal_job_id is no longer available by the time VimLeavePre/stop() runs.
TermOpen fires exactly when the terminal job starts, rather than relying on a constant delay that may be too early or too late.
1a1fdf3 to
b3a2d68
Compare
|
Thanks @nickjvandyke! I've addressed your comments 🏓 |
|
Thank you, looks great! I may just check out the branch and move code around a bit according to the long-term provider API I have in mind if that's alright. Specifically |
|
Sure, feel free to make any changes. Looking forward to having this solved 👍 |
|
Interestingly turns out we do need to Thank you for the quality contribution! 😁 |

Summary
When Neovim exits, the
opencode --portprocess stays orphaned in memory instead of being terminated.Root Cause
During
VimLeavePre, snacks.terminal has already cleaned up its internal state, which invalidates the Neovim job. This means:jobstop(job_id)returnstruebut doesn't actually terminate the processjobpid(job_id)returnsnil- Neovim no longer knows which PID the job maps toSolution
Capture the PID (not just job ID) when the terminal opens, and use shell
killto terminate it directly duringVimLeavePre. This bypasses Neovim's job tracking entirely.Changes
_pidat toggle/start time usingjobpid(terminal_job_id)vim.fn.system('kill -TERM ' .. pid)instop()for reliable terminationVimLeavetoVimLeavePrefor earlier cleanupTesting
:Opencode:qa)ps aux | grep "opencode --port"should return nothing