vxcontrol/pentagi · error

failed to copy output: %w

Error message

failed to copy output: %w

What it means

getExecResult copies the exec stream into a buffer with io.Copy; this error wraps any copy failure other than io.EOF. It means the stdout/stderr stream from the exec process was interrupted abnormally before the command's output was fully read.

Source

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

		TTY: true,
	})
	if err != nil {
		return "", fmt.Errorf("failed to attach to exec process: %w", err)
	}
	defer resp.Close()

	dst := bytes.Buffer{}
	errChan := make(chan error, 1)

	go func() {
		_, copyErr := io.Copy(&dst, resp.Reader)
		errChan <- copyErr
	}()

	select {
	case err := <-errChan:
		if err != nil && err != io.EOF {
			return "", fmt.Errorf("failed to copy output: %w", err)
		}
	case <-ctx.Done():
		// Close the response to unblock io.Copy
		resp.Close()

		// Wait for the copy goroutine to finish
		<-errChan

		suggestedTimeout := max(int(timeout.Seconds())-10, 10)
		return "", fmt.Errorf(
			"command execution timeout (%v). Partial output: %s. "+
				"HINT: If this is an interactive command (shell/REPL/listener), use detach=true. "+
				"For long batch commands, wrap with shell timeout utility: 'timeout %d <command>' to ensure clean completion",
			ctx.Err(),
			truncateString(dst.String(), 500),
			suggestedTimeout,
		)
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Retry the command — transient stream resets are common
  2. Check container health and logs (docker logs) for crashes during execution
  3. Verify Docker daemon connectivity (especially for remote/TLS endpoints)
  4. If the command hangs, the timeout path (943) will fire instead; ensure the command terminates

Example fix

// before
out, err := term.ExecCommand(ctx, flowID, cmd, false) // stream died midway
// after
var out string
for attempt := 1; attempt <= 3; attempt++ {
    out, err = term.ExecCommand(ctx, flowID, cmd, false)
    if err == nil || !strings.Contains(err.Error(), "failed to copy output") {
        break
    }
    time.Sleep(time.Second * time.Duration(attempt))
}
Defensive patterns

Strategy: retry

Type guard

func isCopyOutputErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to copy output")
}

Try / catch

out, err := term.ExecCommand(ctx, flowID, cmd, false)
if isCopyOutputErr(err) {
    // transient stream reset — retry with backoff
    time.Sleep(time.Second)
    out, err = term.ExecCommand(ctx, flowID, cmd, false)
}

Prevention

When it happens

Trigger: The goroutine running io.Copy(resp.Reader, &dst) sends a non-EOF error to errChan: the Docker connection dropped mid-stream, the hijacked connection was reset, or the exec process died in a way that broke the stream.

Common situations: Network interruption between client and Docker daemon; container killed/crashed while the command was streaming output; daemon restart; TLS/connection timeouts on remote Docker hosts.

Related errors


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