{"record":{"id":"b0a164d5255ad266","repo":"vxcontrol/pentagi","slug":"failed-to-copy-output-w","errorCode":null,"errorMessage":"failed to copy output: %w","messagePattern":"failed to copy output: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/terminal.go","lineNumber":288,"sourceCode":"\t\tTTY: true,\n\t})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to attach to exec process: %w\", err)\n\t}\n\tdefer resp.Close()\n\n\tdst := bytes.Buffer{}\n\terrChan := make(chan error, 1)\n\n\tgo func() {\n\t\t_, copyErr := io.Copy(&dst, resp.Reader)\n\t\terrChan <- copyErr\n\t}()\n\n\tselect {\n\tcase err := <-errChan:\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn \"\", fmt.Errorf(\"failed to copy output: %w\", err)\n\t\t}\n\tcase <-ctx.Done():\n\t\t// Close the response to unblock io.Copy\n\t\tresp.Close()\n\n\t\t// Wait for the copy goroutine to finish\n\t\t<-errChan\n\n\t\tsuggestedTimeout := max(int(timeout.Seconds())-10, 10)\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"command execution timeout (%v). Partial output: %s. \"+\n\t\t\t\t\"HINT: If this is an interactive command (shell/REPL/listener), use detach=true. \"+\n\t\t\t\t\"For long batch commands, wrap with shell timeout utility: 'timeout %d <command>' to ensure clean completion\",\n\t\t\tctx.Err(),\n\t\t\ttruncateString(dst.String(), 500),\n\t\t\tsuggestedTimeout,\n\t\t)\n\t}","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/terminal.go#L270-L306","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the command — transient stream resets are common","Check container health and logs (docker logs) for crashes during execution","Verify Docker daemon connectivity (especially for remote/TLS endpoints)","If the command hangs, the timeout path (943) will fire instead; ensure the command terminates"],"exampleFix":"// before\nout, err := term.ExecCommand(ctx, flowID, cmd, false) // stream died midway\n// after\nvar out string\nfor attempt := 1; attempt <= 3; attempt++ {\n    out, err = term.ExecCommand(ctx, flowID, cmd, false)\n    if err == nil || !strings.Contains(err.Error(), \"failed to copy output\") {\n        break\n    }\n    time.Sleep(time.Second * time.Duration(attempt))\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isCopyOutputErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to copy output\")\n}","tryCatchPattern":"out, err := term.ExecCommand(ctx, flowID, cmd, false)\nif isCopyOutputErr(err) {\n    // transient stream reset — retry with backoff\n    time.Sleep(time.Second)\n    out, err = term.ExecCommand(ctx, flowID, cmd, false)\n}","preventionTips":["Retry once or twice — stream resets are frequently transient","Keep the Docker client and daemon on a stable, low-latency connection","Watch for container OOM/kills during long commands (docker events)","For remote daemons, ensure keepalives and sane TLS timeouts"],"tags":["docker","io","stream","network"],"backgroundTag":"docker-stream-interrupted","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}