Skip to content

feat!: stream image build, pull and push progress - #25

Merged
mokevnin merged 1 commit into
mainfrom
feat/image-progress-streams
Sep 27, 2026
Merged

mokevnin merged 1 commit into
mainfrom
feat/image-progress-streams

Conversation

@mokevnin

Copy link
Copy Markdown
Member

images.build, images.pull and images.push returned nil and drained the response, so their progress was lost and a failed build went unnoticed. Docker reports a build failure with status 200 and an error in the last message:

{"stream":" ---> Running in 20f21f18cf3e\n"}
{"error":"...returned a non-zero code: 3","errorDetail":{"code":3,"message":"...returned a non-zero code: 3"}}
  • Streaming. The three methods read the stream to the end, yield each typed message to an optional block and raise Errors::StreamError (with detail) on errorDetail or the legacy error. They still read to the end without a block, because a lazily returned stream left unread could cut a pull short.
  • Models. New BuildInfo, CreateImageInfo, PushImageInfo, ErrorDetail, ProgressDetail and ImageID, from the definitions in the Engine API. BuildInfo#aux is ImageID | String: the classic builder sends an image id, BuildKit a trace.
  • Internal::JSONLStream. The existing request(stream:) now gets the raw lines, because Docker sends its progress streams as application/json and decode_content would join them into one document.
  • BuildKit. Helpers::BuildkitStatus.decode reads the base64 protobuf StatusResponse (moby/buildkit control.proto): vertexes and logs. PlainPrinter renders it as --progress=plain does:
    #4 [2/2] RUN echo from-sdk
    #4 from-sdk
    #4 DONE
    
  • Fix in Union#coerce. The error of a failed variant attempt survived a later variant matching, so reading BuildInfo#aux as a String raised. Each attempt now starts clean, and the chosen alternative keeps its own error.

Checked against Docker Engine 29.8 (API 1.56): pull, a BuildKit build with its log, a failing build raising StreamError, and a push to a local registry:2. The BuildKit test decodes messages captured from that daemon.

rake lint (rubocop, Sorbet, Steep) and rake test (189 runs, 0 failures) pass.

🤖 Generated with Claude Code

images.build, images.pull and images.push returned nil and drained the
response, so their logs were lost and a failed build went unnoticed: Docker
reports it with status 200 and an error in the last line of the stream.

They now read the stream to the end, yield each typed message (BuildInfo,
CreateImageInfo, PushImageInfo) to an optional block and raise
Errors::StreamError when the daemon reports a failure. Reading to the end is
kept on purpose: a lazily returned stream left unread could cut a pull short.

BuildKit sends its progress as base64 protobuf StatusResponse messages in
BuildInfo#aux; Helpers::BuildkitStatus decodes the fields a log needs and
PlainPrinter renders them as --progress=plain does.

Union#coerce also kept the error of a failed variant attempt after a later
variant matched, so reading BuildInfo#aux as a String raised.

BREAKING CHANGE: images.build, images.pull and images.push raise
Errors::StreamError on a failure reported inside the stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mokevnin
mokevnin merged commit 3e3c940 into main Sep 27, 2026
3 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant