Skip to content

fix: do not close caller-owned writer in container DefaultHandler#2608

Open
officialasishkumar wants to merge 1 commit into
buildpacks:mainfrom
officialasishkumar:fix/log-writer-not-closed
Open

fix: do not close caller-owned writer in container DefaultHandler#2608
officialasishkumar wants to merge 1 commit into
buildpacks:mainfrom
officialasishkumar:fix/log-writer-not-closed

Conversation

@officialasishkumar
Copy link
Copy Markdown

Summary

When pack runs a lifecycle phase, container.DefaultHandler copies the container's stdout/stderr into the phase writers and, once copying finishes, calls optionallyCloseWriter on each writer. That helper closed any writer that implemented io.Closer.

For untrusted builders the phase writers are *logging.PrefixWriter, whose Close only flushes a buffered partial line and leaves the wrapped writer open, so closing them is correct and necessary. For trusted builders, however, the logger's writer is passed through unwrapped. When a consumer builds the logger with os.Stdout (an *os.File, and therefore an io.Closer), running a build closed os.Stdout as a side effect.

The pack CLI never noticed because it exits immediately afterwards, but it is surprising and breaks programs that embed pack as a library and expect the writer they passed in to stay open (reported in #1214).

This change restricts optionallyCloseWriter to *logging.PrefixWriter so that only the buffering writers pack itself creates are flushed, and writers owned by the caller are left untouched.

Output

Using pack as a library with a trusted builder:

packClient, _ := pack.NewClient(pack.WithLogger(logging.NewSimpleLogger(os.Stdout)))
_ = packClient.Build(ctx, opts)
// continue using os.Stdout...

Before

os.Stdout is closed once Build returns, so any subsequent writes to it fail.

After

os.Stdout stays open after Build returns. Prefixed phase output for untrusted builders is still flushed exactly as before.

Documentation

  • Should this change be documented?
    • Yes, see #___
    • No

Related

Resolves #1214

@officialasishkumar officialasishkumar requested review from a team as code owners May 20, 2026 07:42
@github-actions github-actions Bot added the type/enhancement Issue that requests a new feature or improvement. label May 20, 2026
@github-actions github-actions Bot added this to the 0.41.0 milestone May 20, 2026
When pack runs a lifecycle phase, the container DefaultHandler copies the
container's stdout/stderr to the phase writers and, once copying finishes,
calls optionallyCloseWriter on each. That helper closed any writer that
implemented io.Closer.

For untrusted builders the phase writers are *logging.PrefixWriter, whose
Close only flushes a buffered partial line and leaves the wrapped writer
open, so closing them is correct. For trusted builders, however, the
logger's writer is passed through unwrapped. When a library consumer
constructs the logger with os.Stdout (an *os.File, and therefore an
io.Closer), running a build closed os.Stdout as a side effect. The pack
CLI did not notice because it exits right after, but it is surprising and
breaks programs that embed pack and expect their writer to stay open.

Restrict optionallyCloseWriter to *logging.PrefixWriter so only the
buffering writers pack creates are flushed, and writers owned by the
caller are left untouched. Add unit tests covering both cases.

Resolves buildpacks#1214

Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
@officialasishkumar officialasishkumar force-pushed the fix/log-writer-not-closed branch from f9f1d39 to 441b8d7 Compare May 20, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/enhancement Issue that requests a new feature or improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pack.Client::Build() closes logger's io.Writer

1 participant