[PR #101] [MERGED] fix(attach): stdcopy-frame replayed logs on non-TTY attach #100

Closed
opened 2026-10-15 12:52:01 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/LXC2Docker/pull/101
Author: @JBailes
Created: 7/1/2026
Status: ✅ Merged
Merged: 7/1/2026
Merged by: @JBailes

Base: main ← Head: fix/attach-logs-stdcopy-framing


📝 Commits (1)

  • f4d5d5d fix(attach): stdcopy-frame replayed logs on non-TTY attach

📊 Changes

2 files changed (+153 additions, -16 deletions)

View changed files

➕ internal/api/attach_framing_test.go (+94 -0)
📝 internal/api/containers.go (+59 -16)

📄 Description

Problem

POST /containers/{id}/attach?logs=1&stream=1 (the docker run foreground and docker attach path) replayed the container's historical console log with a raw io.Copy(conn, f), but the live stream that follows frames non-TTY output with Docker's 8-byte stdcopy header (stream id + big-endian length).

A stdcopy-demuxing client — the docker CLI or any SDK ContainerAttach + stdcopy.StdCopy against a non-TTY container — reads the first 8 bytes of replayed log content as a frame header, gets a garbage length, and desyncs. The rest of the attach stream is corrupted.

Docker behavior

Docker frames the replayed logs identically to the live stream: raw for TTY containers, stdcopy-framed for non-TTY. There is no unframed/framed boundary.

Fix

  • Load the container record up front so its Tty flag governs framing for both the replay and the live loop.
  • Route the replay through a shared writeFramedStream helper and the live loop through writeStreamFrame, so both sides frame identically.
  • No behavior change for TTY containers (still raw) or for stream/logs flag handling.

Tests

internal/api/attach_framing_test.go:

  • TTY frame is raw; non-TTY frame has the correct stdcopy header + length.
  • Regression: a 64 KiB replay (spans multiple 32 KiB reads) must be parseable as a clean sequence of stdcopy frames that reassemble to the original log.

make test-build, make test-unit, go vet, go build ./... all green locally.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/LXC2Docker/pull/101 **Author:** [@JBailes](https://github.com/JBailes) **Created:** 7/1/2026 **Status:** ✅ Merged **Merged:** 7/1/2026 **Merged by:** [@JBailes](https://github.com/JBailes) **Base:** `main` ← **Head:** `fix/attach-logs-stdcopy-framing` --- ### 📝 Commits (1) - [`f4d5d5d`](https://github.com/games-on-whales/LXC2Docker/commit/f4d5d5d25abcb4ce3e25380954f11b9c51525a8c) fix(attach): stdcopy-frame replayed logs on non-TTY attach ### 📊 Changes **2 files changed** (+153 additions, -16 deletions) <details> <summary>View changed files</summary> ➕ `internal/api/attach_framing_test.go` (+94 -0) 📝 `internal/api/containers.go` (+59 -16) </details> ### 📄 Description ## Problem `POST /containers/{id}/attach?logs=1&stream=1` (the `docker run` foreground and `docker attach` path) replayed the container's historical console log with a **raw** `io.Copy(conn, f)`, but the live stream that follows frames non-TTY output with Docker's 8-byte **stdcopy** header (stream id + big-endian length). A stdcopy-demuxing client — the `docker` CLI or any SDK `ContainerAttach` + `stdcopy.StdCopy` against a **non-TTY** container — reads the first 8 bytes of replayed log content as a frame header, gets a garbage length, and desyncs. The rest of the attach stream is corrupted. ## Docker behavior Docker frames the replayed logs **identically** to the live stream: raw for TTY containers, stdcopy-framed for non-TTY. There is no unframed/framed boundary. ## Fix - Load the container record up front so its `Tty` flag governs framing for **both** the replay and the live loop. - Route the replay through a shared `writeFramedStream` helper and the live loop through `writeStreamFrame`, so both sides frame identically. - No behavior change for TTY containers (still raw) or for `stream`/`logs` flag handling. ## Tests `internal/api/attach_framing_test.go`: - TTY frame is raw; non-TTY frame has the correct stdcopy header + length. - **Regression:** a 64 KiB replay (spans multiple 32 KiB reads) must be parseable as a clean sequence of stdcopy frames that reassemble to the original log. `make test-build`, `make test-unit`, `go vet`, `go build ./...` all green locally. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/LXC2Docker#100
No description provided.