hashicorp/terraform · error
failed to send completed event: %w
Error message
failed to send completed event: %w
What it means
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.
Source
Thrown at internal/grpcwrap/provider6.go:1261
server.Send(&tfplugin6.InvokeAction_Event{
Type: &tfplugin6.InvokeAction_Event_Progress_{
Progress: &tfplugin6.InvokeAction_Event_Progress{
Message: invokeEvt.Message,
},
},
})
case providers.InvokeActionEvent_Completed:
completed := &tfplugin6.InvokeAction_Event_Completed{}
completed.Diagnostics = convert.AppendProtoDiag(completed.Diagnostics, invokeEvt.Diagnostics)
err := server.Send(&tfplugin6.InvokeAction_Event{
Type: &tfplugin6.InvokeAction_Event_Completed_{
Completed: completed,
},
})
if err != nil {
return fmt.Errorf("failed to send completed event: %w", err)
}
}
}
return nil
}
func (p *provider6) ValidateActionConfig(_ context.Context, req *tfplugin6.ValidateActionConfig_Request) (*tfplugin6.ValidateActionConfig_Response, error) {
resp := &tfplugin6.ValidateActionConfig_Response{}
ty := p.schema.Actions[req.TypeName].ConfigSchema.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
View on GitHub (pinned to c9def3e214)
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.
Example fix
// before
err := server.Send(&tfplugin6.InvokeAction_Event{
Type: &tfplugin6.InvokeAction_Event_Completed_{Completed: completed},
}) // client already gone -> failed to send completed event: ...
// after (client side: always receive until EOF)
for {
ev, err := stream.Recv()
if err == io.EOF { break }
if err != nil { return err }
handle(ev)
} Defensive patterns
Strategy: retry
Validate before calling
// Before sending the Completed event, confirm the stream context is live:
func streamReady(ctx context.Context) bool { return ctx.Err() == nil } Type guard
func isClientGone(err error) bool {
return errors.Is(err, context.Canceled) || status.Code(err) == codes.Unavailable
} Try / catch
// Clients should drain the stream; if send fails on a transient error, retry the action:
for attempt := 0; attempt < 3; attempt++ {
err := runInvokeAction(ctx, req)
if err == nil || !isClientGone(err) { return err }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- wrapped err: %w
- error writing state: %w
- Failed to read state file from %v: %v
- Failed to read remote state: %s
- unable to read 'content' from response: %w
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/920d9d9c770a8e52.
Report an issue: GitHub.