vxcontrol/pentagi · error

failed to read file '%s' content: %w

Error message

failed to read file '%s' content: %w

What it means

After allocating fileContent of tarHeader.Size bytes, the code reads the tar entry body; any read error other than io.EOF (which is normal for exact-size reads) is wrapped with this message. io.EOF is intentionally accepted because tar.Reader returns EOF exactly when the full entry was consumed, so this error means the stream died mid-entry.

Source

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

			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)
		}
		if tarHeader.Size < 0 {
			return "", fmt.Errorf("file '%s' has invalid size %d", tarHeader.Name, tarHeader.Size)
		}

		var fileContent = make([]byte, tarHeader.Size)
		_, err = tarReader.Read(fileContent)
		if err != nil && err != io.EOF {
			return "", fmt.Errorf("failed to read file '%s' content: %w", tarHeader.Name, err)
		}
		buffer.Write(fileContent)

		if stats.Mode.IsDir() {
			buffer.WriteString("\n\n")
		}
	}

	return buffer.String(), nil
}

func (t *terminal) WriteFile(ctx context.Context, flowID int64, content string, path string) (string, error) {
	if path == "" {
		return "", fmt.Errorf("path is required and cannot be empty")
	}

	if err := t.writeFileToContainer(ctx, flowID, path, content); err != nil {
		return "", err

View on GitHub (pinned to ea665308ba)

Solutions

  1. Retry the entire read (CopyFromContainer + tar loop) — mid-stream truncation is usually transient
  2. Use errors.Is(err, io.ErrUnexpectedEOF) to distinguish truncation from other IO errors and log accordingly
  3. Check Docker daemon logs and host disk health (`dmesg`, `docker info`) when it recurs
  4. For large files, stream with io.CopyFull / io.ReadFull into the buffer with timeouts instead of a single Read

Example fix

// before
_, err = tarReader.Read(fileContent)
if err != nil && err != io.EOF {
    return "", fmt.Errorf("failed to read file '%s' content: %w", tarHeader.Name, err)
}
// after
if _, err := io.ReadFull(tarReader, fileContent); err != nil && err != io.EOF && !errors.Is(err, io.ErrUnexpectedEOF) {
    return "", fmt.Errorf("failed to read file '%s' content: %w", tarHeader.Name, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-call: confirm daemon connectivity to reduce mid-stream failures
if _, err := dockerClient.Ping(ctx); err != nil {
    return fmt.Errorf("docker daemon unstable: %w", err)
}

Type guard

func isMidStreamIO(err error) bool {
    return err != nil && !errors.Is(err, io.EOF) && (errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.EPIPE))
}

Try / catch

content, err := tool.ReadFile(ctx, flowID, path)
if err != nil && strings.Contains(err.Error(), "failed to read file") {
    if isRetryable(err) {
        content, err = tool.ReadFile(ctx, flowID, path) // one retry
    }
}

Prevention

When it happens

Trigger: The tar stream from CopyFromContainer terminates before tarHeader.Size bytes are delivered — connection reset to a remote daemon, daemon restart, reader closed by timeout, or disk/IO error on the Docker host while assembling the archive.

Common situations: Reading a large file from a container on a remote/unstable Docker host; host under memory pressure killing the daemon mid-copy; container removed or restarted concurrently with the copy; misconfigured proxy between client and daemon cutting long-lived streams.

Related errors


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