From 45bc9fe27c80fdfb344e3435419906eca36406df Mon Sep 17 00:00:00 2001 From: mram Date: Mon, 21 Sep 2026 22:29:41 +0200 Subject: [PATCH] Strip ANSI escapes from managed ComfyUI output in the log; spawn child with NO_COLOR/TERM=dumb --- internal/supervise/supervise.go | 19 ++++++++++++++++--- internal/supervise/supervise_test.go | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/internal/supervise/supervise.go b/internal/supervise/supervise.go index ad13401..c2f4e3c 100644 --- a/internal/supervise/supervise.go +++ b/internal/supervise/supervise.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" "strings" "sync" @@ -185,6 +186,9 @@ func (p *Process) EnsureRunning() error { p.external = false cmd := exec.Command(p.argv[0], p.argv[1:]...) cmd.Dir = p.dir + // Ask the child not to colorize (ComfyUI ignores this and colors + // anyway, so pipeLog also strips escape sequences). + cmd.Env = append(os.Environ(), "NO_COLOR=1", "TERM=dumb") stdout, err := cmd.StdoutPipe() if err != nil { return err @@ -278,7 +282,9 @@ func (p *Process) WatchIdle(ctx context.Context, idleTimeout time.Duration, gpuI } // pipeLog forwards one child output stream to the log at INFO, line by -// line, prefixed with the process name. +// line, prefixed with the process name. ANSI escape sequences are +// stripped: ComfyUI colorizes unconditionally, and the escapes only +// render as garbage in a log file. func (p *Process) pipeLog(r io.Reader) { buf := make([]byte, 4096) var line string @@ -290,18 +296,25 @@ func (p *Process) pipeLog(r io.Reader) { if i < 0 { break } - p.log.Info(p.name + ": " + strings.TrimRight(line[:i], "\r")) + p.log.Info(p.name + ": " + stripANSI(strings.TrimRight(line[:i], "\r"))) line = line[i+1:] } if err != nil { if strings.TrimSpace(line) != "" { - p.log.Info(p.name + ": " + line) + p.log.Info(p.name + ": " + stripANSI(line)) } return } } } +// ansiPattern matches CSI escape sequences (colors, cursor moves, …). +var ansiPattern = regexp.MustCompile("\x1b\\[[0-9;?]*[a-zA-Z]") + +func stripANSI(s string) string { + return ansiPattern.ReplaceAllString(s, "") +} + // stopTree kills cmd's process, including its children on Windows (python // launchers tend to spawn some). The Wait goroutine reaps it. func stopTree(cmd *exec.Cmd) { diff --git a/internal/supervise/supervise_test.go b/internal/supervise/supervise_test.go index 8df34d4..ffb20ef 100644 --- a/internal/supervise/supervise_test.go +++ b/internal/supervise/supervise_test.go @@ -239,3 +239,17 @@ func TestComfyLayoutAndDefaultCommand(t *testing.T) { t.Errorf("missing-layout script = %s", script) } } + +func TestStripANSI(t *testing.T) { + cases := map[string]string{ + "\x1b[32m[INFO]\x1b[0m Starting server": "[INFO] Starting server", + "\x1b[1m\x1b[31m[ERROR]\x1b[0m boom": "[ERROR] boom", + "plain line": "plain line", + "\x1b[33mWARN\x1b[0m: \x1b[1mbold\x1b[0m": "WARN: bold", + } + for in, want := range cases { + if got := stripANSI(in); got != want { + t.Errorf("stripANSI(%q) = %q, want %q", in, got, want) + } + } +}