You can check what happens using strace:
strace -o wtf-trace.txt -ff bash -c '{ (echo wtf) > /dev/stdout; } >> wtf.txt'
In my case, this will lead to the creation of two files, such as wtf-trace.txt.12889and wtf-trace.txt.12890. What happens, process 1 >> wtf.txt:
open("wtf.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
dup2(3, 1) = 1
close(3) = 0
clone(child_stack=0, .................) = 12890
wait4(-1, [{WIFEXITED(s) .............) = 12890
exit_group(0) = ?
"wtf.txt" FD 3. FD 1 FD 3 FD 3. (), .
{ echo wtf > /dev/stdout } FD 1 (stdout), :
open("/dev/stdout", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
dup2(3, 1) = 1
close(3) = 0
fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(1, "wtf\n", 4) = 4
exit_group(0) = ?
, /dev/stdout (note O_TRUNC) FD 3, dup2, FD 3 FD 1, FD 3, FD 1 0 st_size=0, .
| cat >>, FD 1, , ...
NB: strace.