testing: `go test -v` doubles ESC (0x1b) in t.Log output since Go 1.27, corrupting ANSI colors in some terminals
Go version
go version go1.27.1-X:nodwarf5 linux/arm64
Output of go env in your module/workspace:
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN='/home/user/go/bin'
GOCACHE='/home/user/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/user/.config/go/env'
GOEXE=''
GOEXPERIMENT='nodwarf5'
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -pthread -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3411497907=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='arm64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/user/esc/go.mod'
GOMODCACHE='/home/user/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPACKAGESDRIVER=''
GOPATH='/home/user/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='off'
GOTELEMETRYDIR='/home/user/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go/pkg/tool/linux_arm64'
GOVCS=''
GOVERSION='go1.27.1-X:nodwarf5'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
Logged a line containing ANSI SGR escape sequences through t.Log and ran the test with go test -v in a terminal. Found through a logging library whose test helper writes the output of a colored log/slog handler through testing.TB.Log: the colors looked right in Apple Terminal but came out corrupted in kitty.
Minimal reproducer:
package escrepro
import (
"fmt"
"testing"
)
func TestColor(t *testing.T) {
t.Log("\x1b[92mgreen via t.Log\x1b[0m")
fmt.Println("\x1b[92mgreen via fmt.Println\x1b[0m") // comparison: bypasses the testing package
}
go test -count=1 -v . | cat -v # cat -v shows ESC (0x1b) as ^[
What did you see happen?
With Go 1.27.1 every ESC in the t.Log line is doubled, while the fmt.Println line is intact:
=== RUN TestColor
esc_test.go:9: ^[^[[92mgreen via t.Log^[^[[0m
^[[92mgreen via fmt.Println^[[0m
--- PASS: TestColor (0.00s)
PASS
ok example.com/escrepro 0.002s
od -c of the t.Log line:
0000000 e s c _ t e s t . g o :
0000020 9 : 033 033 [ 9 2 m g r e e n v
0000040 i a t . L o g 033 033 [ 0 m \n
With Go 1.26.0 (GOTOOLCHAIN=go1.26.0 go test -count=1 -v . | cat -v) the bytes come out as logged:
=== RUN TestColor
esc_test.go:9: ^[[92mgreen via t.Log^[[0m
^[[92mgreen via fmt.Println^[[0m
--- PASS: TestColor (0.00s)
Further observations:
- Running the compiled test binary directly (
go test -c -o esc.test . && ./esc.test -test.v | cat -v) shows the same doubled ESC, so it is the testing package, not cmd/go. go test -jsonis fine: test2json strips the escapes and the JSONOutputfield contains\u001b[92monce. Only the human-readable-vstream is affected.t.Error,t.Fatalandt.Output()go through the same code path ast.Log.- Terminals differ in how they cope. Apple Terminal, like xterm and other DEC-style parsers, treats an ESC arriving inside an escape sequence as the start of a new one, so
ESC ESC [92mstill renders green and the corruption goes unnoticed. kitty does not tolerate the doubled ESC and the colored lines come out corrupted:
- Anything that consumes the raw
-vstream sees corrupted data, e.g. a test running a childgo test -von a pseudo-terminal and asserting on its output.
Cause: (*outputWriter).writeLine in src/testing/testing.go calls escapeMarkers(b) whenever the test runs in chatty (-v) mode, regardless of chatty.json. escapeMarkers inserts ^[ before each of ^V, ^O, ^N and ^[.
This arrived in Go 1.27 with CL 751940 ("testing: escapes framing markers", for #62728), whose stated purpose is to keep such bytes from corrupting "the test2json parse of the enclosing run". But only test2json (-test.v=test2json) removes the escapes; in plain -test.v=true mode nothing does. A few lines further down in the same function the ^O/^N error markers are correctly gated on o.c.chatty.json; the escaping is not. The code is unchanged on master and release-branch.go1.27 as of 2026-09-17.
This is a regression from Go 1.26.
What did you expect to see?
The same output as Go 1.26: go test -v prints the logged bytes unchanged apart from the indentation and the file:line: prefix, and the framing escapes are applied only in -test.v=test2json mode, where test2json decodes them. For example, in writeLine:
// Escape the framing marker.
if o.c.chatty.json {
b = escapeMarkers(b)
}
Source: golang/go