I need to save all lines of a terminal buffer into a list after its job finishes asynchronously. I,m opening the terminal buffer like this:

  let term_buf_no = term_start(cmd, {'term_name': name, 'term_rows': 10, 'exit_cb': 'ValidateQuickfix'})

and the function

function ValidateQuickfix(job, exit_status)
  let job = a:job
  let exit_status = a:exit_status
  call s:dbg('job, exit status -> ' . job . ', ' . exit_status)

just returns

DEBUG: job, exit status -> process 6323 dead, 2

But how can i get the terminal buffer number inside this callback?

  • 2
    see :h job-options, try to use out_cb
    – Maxim Kim
    Commented Jun 24, 2022 at 14:04
  • 1
    Also :help job-functions for manipulating the job object
    – D. Ben Knoble
    Commented Jun 24, 2022 at 14:11
  • 1
    Also :help :make for running a program synchronously and putting the output in the quickfix list (via :help errorformat and others)
    – D. Ben Knoble
    Commented Jun 24, 2022 at 14:12
  • If you’ve ended up with extra accounts we can help you merge them.
    – D. Ben Knoble
    Commented Jan 27, 2023 at 13:17

2 Answers 2


The job you get on call-back is a Job object. When used as a string, e.g:

echo "Job: " . a:job

you get the PID. You can however get a lot of information from the Job spec using various functions, for example job_info(a:job). For example:

job {
  status             : 'dead'                       # String
  cmd                [                              # List
    0                  : 'ls'                       # String
  termsig            : ''                           # String
  stoponexit         : 'term'                       # String
  tty_out            : '/dev/pts/31'                # String
  exitval            :  0                           # Number
  exit_cb            : '<SNR>123_ValidateQuickfix'  # String
  tty_in             : '/dev/pts/31'                # String
  channel            {                              # Channel
    status             : 'buffered'                 # String
    id                 :  2                         # Number
    in_io              : 'pipe'                     # String
    err_mode           : 'RAW'                      # String
    in_status          : 'closed'                   # String
    out_io             : 'buffer'                   # String
    err_io             : 'buffer'                   # String
    err_status         : 'closed'                   # String
    out_timeout        :  2000                      # Number
    out_status         : 'buffered'                 # String
    out_mode           : 'RAW'                      # String
    in_mode            : 'RAW'                      # String
    in_timeout         :  2000                      # Number
    err_timeout        :  2000                      # Number
  process            :  15695                       # Number

In your case you can use ch_getbufnr(a:job, "out") - this returns the buffer number for the process.

At least on my tests there is a caveat though. If out_io is not set to "buffer" there is no guarantee the buffer has been written to on call-back. With it set to "buffer" the terminal window itself would be empty (as it writes to another buffer).

Testing with exit_cb and something like this:

fun! s:SafeRead(bufnr)
    let txt = getbufline(a:bufnr, 1, '$')
fun! s:ValidateQuickfix(job, exit_status)
    let bufnr = ch_getbufnr(a:job, "out")
    exe "autocmd SafeState * ++once call s:SafeRead(" . bufnr . ")"

the buffer is empty. Using close_cb and the same routine the text is present, but not sure how reliable that is. Without the autocmd routine the buffer is always empty on call-back. (You could perhaps do some flush routine).

Best approach would likely be to use some combination of "out_io": "buffer". That would of course render the terminal rather useless at is empty.

You can also use ch_readraw() to read the contents from the buffer. :h channel-more. This would also render terminal empty.

You could also read specific stream, e.g: ch_readraw(a:job, {"part":"err"})


The term_wait() function solved it. This functions specifies the number of milliseconds to wait before the terminal buffer is considered synchronized. Default is 10, after increasing it to 100, its content is completely read and its last line is really the last one and i can use it in a script further on. Therefore, inside the function defined in exit_cb parameter, run the following:

let term_buf_no = ch_getbufnr(job, 'out')
call term_wait(term_buf_no, 100)
let result = getbufline(term_buf_no, 1, '$')
  • Details on how would be nice.
    – D. Ben Knoble
    Commented Jan 27, 2023 at 13:17
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Jan 27, 2023 at 13:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.