vxcontrol/pentagi · error

failed to read tar header: %w

Error message

failed to read tar header: %w

What it means

After CopyFromContainer returns a tar stream, readFileFromContainer iterates it with tar.Reader.Next(). Any decode error that is not io.EOF — meaning the byte stream Docker returned is not a valid/truncated tar archive — is wrapped with this message. It indicates corruption between the daemon and the reader, not a problem with the file itself.

Source

Thrown at backend/pkg/tools/terminal.go:388

	if !isRunning {
		return "", fmt.Errorf("container runtime is not operational")
	}

	reader, stats, err := t.dockerClient.CopyFromContainer(ctx, containerName, path)
	if err != nil {
		return "", fmt.Errorf("failed to copy file: %w", err)
	}
	defer reader.Close()

	var buffer strings.Builder
	tarReader := tar.NewReader(reader)
	for {
		tarHeader, err := tarReader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return "", fmt.Errorf("failed to read tar header: %w", err)
		}

		if tarHeader.FileInfo().IsDir() {
			continue
		}

		if stats.Mode.IsDir() {
			buffer.WriteString("--------------------------------------------------\n")
			buffer.WriteString(
				fmt.Sprintf("'%s' file content (with size %d bytes) shown below:\n",
					tarHeader.Name, tarHeader.Size,
				),
			)
		}

		const maxReadFileSize int64 = 100 * 1024 * 1024 // 100 MB limit
		if tarHeader.Size > maxReadFileSize {
			return "", fmt.Errorf("file '%s' size %d exceeds maximum allowed size %d", tarHeader.Name, tarHeader.Size, maxReadFileSize)

View on GitHub (pinned to ea665308ba)

Solutions

  1. Retry the read — truncation is often transient; wrap the whole CopyFromContainer+tar loop in a retry with backoff
  2. Avoid tar-reading pseudo-filesystem entries; target regular files (check tarHeader.Typeflag / FileInfo().Mode())
  3. Check the Docker daemon logs and connectivity to the remote host for mid-stream disconnects
  4. If reading large files, read them in chunks or increase client timeouts rather than letting the stream die

Example fix

// before
_, err = tarReader.Read(fileContent)
// after
tarHeader, err := tarReader.Next()
if err == io.EOF {
    break
}
if err != nil {
    if errors.Is(err, io.ErrUnexpectedEOF) {
        return "", fmt.Errorf("truncated tar stream from container %s, retry: %w", containerName, err)
    }
    return "", fmt.Errorf("failed to read tar header: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// No pre-call validation possible; optionally verify daemon connectivity first:
if _, err := dockerClient.Ping(ctx); err != nil {
    return fmt.Errorf("docker daemon unreachable, fix before reading: %w", err)
}

Type guard

func isTruncatedTar(err error) bool {
    return errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, tar.ErrHeader)
}

Try / catch

var content string
var err error
for attempt := 0; attempt < 3; attempt++ {
    content, err = tool.ReadFile(ctx, flowID, path)
    if err == nil || !strings.Contains(err.Error(), "failed to read tar header") {
        break
    }
    time.Sleep(time.Duration(1<<attempt) * 500 * time.Millisecond)
}

Prevention

When it happens

Trigger: The tar stream from CopyFromContainer was truncated (network hiccup to a remote Docker host, daemon killed mid-response), the reader was closed early, or a non-archive response was produced for special files (device/pipe entries the daemon cannot serialize).

Common situations: Reading from a container on a flaky remote Docker host (DOCKER_HOST over TCP/TLS); reading pseudo-files from /proc, /sys, or socket/fifo entries that don't tar cleanly; reading very large files over an unstable connection; a concurrent `docker cp`/container removal interrupted the stream.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/045ad939ca002f63. Report an issue: GitHub.