From 433a84d8244420bad69d6926ea76ee228b389a64 Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 27 Oct 2026 11:08:27 -0400 Subject: [PATCH] matugen: don't wait on hooks ref #3901 port 2.0 (cherry picked from commit 86dac60bc26d43c0e1b230807821b07ff74c120e) --- core/internal/matugen/matugen.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/core/internal/matugen/matugen.go b/core/internal/matugen/matugen.go index bbb4d2ea9..9e968316b 100644 --- a/core/internal/matugen/matugen.go +++ b/core/internal/matugen/matugen.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "math" "os" "os/exec" @@ -1147,21 +1146,32 @@ func runMatugen(baseArgs []string, sourceMode string) error { // matugen's stderr is echoed as before and its tail travels in the error, so a failure // reaching the shell through the socket still names the broken template or option. +// It goes through a file, not a pipe: with a pipe, Wait blocks until every hook +// child that inherited stderr exits, and a backgrounded post_hook never does (#3901). func execMatugen(args []string) error { + stderrFile, err := os.CreateTemp("", "matugen-stderr-*") + if err != nil { + return fmt.Errorf("matugen: %w", err) + } + defer os.Remove(stderrFile.Name()) + defer stderrFile.Close() + cmd := exec.Command("matugen", args...) cmd.Env = utils.EnvWithUserBinPath(nil) cmd.Stdout = os.Stdout - var stderr bytes.Buffer - cmd.Stderr = io.MultiWriter(os.Stderr, &stderr) - err := cmd.Run() - if err == nil { + cmd.Stderr = stderrFile + runErr := cmd.Run() + + stderr, _ := os.ReadFile(stderrFile.Name()) + os.Stderr.Write(stderr) + if runErr == nil { return nil } - tail := stderrTail(stderr.String(), 6) + tail := stderrTail(string(stderr), 6) if tail == "" { - return fmt.Errorf("matugen: %w", err) + return fmt.Errorf("matugen: %w", runErr) } - return fmt.Errorf("matugen: %w\n%s", err, tail) + return fmt.Errorf("matugen: %w\n%s", runErr, tail) } var ansiEscapeRe = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)