{"record":{"id":"920d9d9c770a8e52","repo":"hashicorp/terraform","slug":"failed-to-send-completed-event-w","errorCode":null,"errorMessage":"failed to send completed event: %w","messagePattern":"failed to send completed event: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/grpcwrap/provider6.go","lineNumber":1261,"sourceCode":"\t\t\tserver.Send(&tfplugin6.InvokeAction_Event{\n\t\t\t\tType: &tfplugin6.InvokeAction_Event_Progress_{\n\t\t\t\t\tProgress: &tfplugin6.InvokeAction_Event_Progress{\n\t\t\t\t\t\tMessage: invokeEvt.Message,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\n\t\tcase providers.InvokeActionEvent_Completed:\n\t\t\tcompleted := &tfplugin6.InvokeAction_Event_Completed{}\n\t\t\tcompleted.Diagnostics = convert.AppendProtoDiag(completed.Diagnostics, invokeEvt.Diagnostics)\n\n\t\t\terr := server.Send(&tfplugin6.InvokeAction_Event{\n\t\t\t\tType: &tfplugin6.InvokeAction_Event_Completed_{\n\t\t\t\t\tCompleted: completed,\n\t\t\t\t},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to send completed event: %w\", err)\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn nil\n}\n\nfunc (p *provider6) ValidateActionConfig(_ context.Context, req *tfplugin6.ValidateActionConfig_Request) (*tfplugin6.ValidateActionConfig_Response, error) {\n\tresp := &tfplugin6.ValidateActionConfig_Response{}\n\tty := p.schema.Actions[req.TypeName].ConfigSchema.ImpliedType()\n\n\tconfigVal, err := decodeDynamicValue6(req.Config, ty)\n\tif err != nil {\n\t\tresp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)\n\t\treturn resp, nil\n\t}\n","sourceCodeStart":1243,"sourceCodeEnd":1279,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/grpcwrap/provider6.go#L1243-L1279","documentation":"Returned by InvokeAction (internal/grpcwrap/provider6.go:1261) when server.Send fails while emitting the Completed event over the InvokeAction server-streaming RPC. The handler iterates provider InvokeAction events; on a Completed event it converts diagnostics to proto and calls server.Send, and if that returns a non-nil error it is wrapped with %w and returned. The cause is a broken/cancelled downstream gRPC stream.","triggerScenarios":"The gRPC client (Terraform core) has disconnected, cancelled the context, or the stream broke exactly when the provider finished and tried to send its terminal Completed event; server.Send returns the transport error.","commonSituations":"User cancels (Ctrl-C) right as an action completes, context deadline exceeded for long-running actions, network drop, or the client process exiting before draining the stream.","solutions":["Distinguish context cancellation (errors.Is(err, context.Canceled)) from real transport failures.","For long actions, ensure gRPC keepalive/deadline are generous enough to reach the Completed send.","Retry the action invocation if idempotent, after verifying the client is still connected.","If authoring a client, always drain the InvokeAction stream to completion before closing."],"exampleFix":"// before\nerr := server.Send(&tfplugin6.InvokeAction_Event{\n    Type: &tfplugin6.InvokeAction_Event_Completed_{Completed: completed},\n})  // client already gone -> failed to send completed event: ...\n\n// after (client side: always receive until EOF)\nfor {\n    ev, err := stream.Recv()\n    if err == io.EOF { break }\n    if err != nil { return err }\n    handle(ev)\n}","handlingStrategy":"retry","validationCode":"// Before sending the Completed event, confirm the stream context is live:\nfunc streamReady(ctx context.Context) bool { return ctx.Err() == nil }","typeGuard":"func isClientGone(err error) bool {\n    return errors.Is(err, context.Canceled) || status.Code(err) == codes.Unavailable\n}","tryCatchPattern":"// Clients should drain the stream; if send fails on a transient error, retry the action:\nfor attempt := 0; attempt < 3; attempt++ {\n    err := runInvokeAction(ctx, req)\n    if err == nil || !isClientGone(err) { return err }\n}","preventionTips":["Always drain InvokeAction streams to EOF before closing.","Keep gRPC keepalive/deadline generous for long actions.","Distinguish user cancellation (context.Canceled) from transport drops.","Retry idempotent actions on transient stream failures."],"tags":["grpc","action","streaming","network","io"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}