#932·air

【BUG】Build command output is fully buffered before being printed

Author: GG-FengCreated Jul 31, 2026Updated Aug 25, 2026

Summary

runCommandCopyOutput() reads the entire build command stdout into memory before writing it to the terminal. For verbose build commands, this can cause unnecessarily high memory usage and delayed output.

Affected Code

In runner/engine.go, runCommandCopyOutput() reads all stdout at once:

go
stdoutBytes, _ := io.ReadAll(stdout)
_, _ = io.Copy(os.Stdout, strings.NewReader(string(stdoutBytes)))

This path is used by building() when running e.config.Build.Cmd.

Safe Validation Evidence

Tested locally against commit b487a2c59460eea59cee389dfb36152eab5b6eb0.

A local package test used a build command that produced large stdout. The test confirmed that the output is accumulated in full before being copied to stdout:

=== RUN   TestReproH7H8_UnboundedCommandOutput
    repro_high_test.go:121: H7/H8 = TRUE POSITIVE: seq 1 100000 = 588895 bytes. engine.go:687 io.ReadAll(stdout) reads ALL with NO LimitReader
--- PASS: TestReproH7H8_UnboundedCommandOutput (0.00s)
PASS
ok  	github.com/air-verse/air/runner	(cached)

The current master branch still contains the same code path at runner/engine.go.

Expected Behavior

Build command output should be streamed or bounded so Air does not need to keep the entire stdout content in memory.

Actual Behavior

Air reads the complete stdout stream into a byte slice before printing it.

Impact

Very verbose build commands can cause high memory usage and delayed terminal output during rebuilds.

Suggested Fix

Stream stdout directly while preserving enough output for the build failure message, or use a bounded buffer for the retained output.