thread::errorproc establishes a handler for errors that occur in commands that are executed via thread::send -async. This handler is process-global: It is shared by all threads, and it can be set from any thread. errorproc will be invoked in whichever thread set errorproc - not the master thread, nor in the thread that generates the error.

The following script illustrates the interaction of thread::errorproc and bgerror when threads raise errors. Each test occurs during a vwait to ensure the event loop is running.

puts "Tcl: [package require Tcl]"
puts "Thread: [package require Thread]"

namespace eval d {}
proc d::bgerror args {puts "\[BGERROR\]: $args"}
proc d::errorproc args {puts "\[THREAD-ERROR\]: $args"}

interp bgerror {} d::bgerror
thread::errorproc d::errorproc

set t0 [thread::create thread::wait]

puts "-------------------------"

after 100 {incr ::done}
after 10 {throw {TEST AFTER} "Error in after!"}
vwait ::done

puts "-------------------------"

after 100 {incr ::done}
thread::send -async $t0 {throw {TEST INTHREAD ASYNC RESULT} "Error in thread (async with result)!"} ::res
vwait ::done
puts "res is $res"

puts "-------------------------"

after 100 {incr ::done}
thread::send -async $t0 {throw {TEST INTHREAD ASYNC VOID} "Error in thread (async void)!"} 
vwait ::done

puts "-------------------------"

after 100 {
    try {
        thread::send $t0 {throw {TEST INTHREAD SYNCHRONOUS} "Error in thread (synchronous)!"} 
    } on error {e o} {
        puts "\[CAUGHT-ERROR\]: $e"
    } finally {
        incr ::done
vwait done

puts "-------------------------"

Note the strange result when -async is used with a result variable (the 2nd test):

Tcl: 8.6.4
Thread: 2.7.2
[BGERROR]: {Error in after!} {-errorcode {TEST AFTER} -code 1 -level 0 -errorstack {INNER {returnImm {Error in after!} {-errorcode {TEST AFTER}}}} -errorinfo {Error in after!
    while executing
"throw {TEST AFTER} "Error in after!""
    ("after" script)} -errorline 1}
[THREAD-ERROR]: tid0x7f379951c700 {Error in thread (async with result)!
    while executing
"throw {TEST INTHREAD ASYNC RESULT} "Error in thread (async with result)!""}
[BGERROR]: {Error in thread (async with result)!} {-code 1 -level 0 -errorstack {INNER {returnImm {Error in after!} {-errorcode {TEST AFTER}}}} -errorcode NONE -errorinfo {Error in thread (async with result)!} -errorline 1}
res is Error in thread (async with result)!
[THREAD-ERROR]: tid0x7f379951c700 {Error in thread (async void)!
    while executing
"throw {TEST INTHREAD ASYNC VOID} "Error in thread (async void)!""}
[CAUGHT-ERROR]: Error in thread (synchronous)!

With the test script:

% thread::send -async $t0 {throw ..} ::res

first thread::errorproc fires, then bgerror. This could be useful, as bgerror sees more information than errorproc ... but the error dictionary is not in great shape:

      -code = 1
     -level = 0
-errorstack = "INNER {returnImm {Error in after!} {-errorcode {TEST AFTER}}}"
 -errorcode = NONE
 -errorinfo = "Error in thread (async with result)!"
 -errorline = 1

it looks like everything but -errorinfo comes from the previous error, and -errorcode is simply wrong. -errorcode NONE might be a good way to filter these errors out if you're using errorproc and bgerror together.

PYK 2015-04-25: It's not that -errorcode is wrong, just that thread only sends the -errorinfo, not the whole return options dictionary to thread::errorproc. When bgerrror is triggered as a result of the failed variable write, a return options dictionary is generated, but isn't going to contain any useful information other than the -errorinfo value, which thread::errorproc also receives.

aspect: this seems to be an effect of [L1 ].