wavetermdev/waveterm · warning
client is done
Error message
client is done
What it means
SendAsyncInitiation checks c.GetIsDone() and refuses to send if the client has already shut down (blockclose event, doShutdown, or explicit close). Once done, the RPC connection is being torn down, so initiating async vdom rendering would target a dead connection. This is a post-shutdown state guard.
Source
Thrown at pkg/waveapp/waveapp.go:234
}
if !gotRoute {
return fmt.Errorf("vdom context route could not be established")
}
wshclient.EventSubCommand(c.RpcClient, wps.SubscriptionRequest{Event: wps.Event_BlockClose, Scopes: []string{
blockORef.String(),
}}, nil)
c.RpcClient.EventListener.On("blockclose", func(event *wps.WaveEvent) {
c.doShutdown("got blockclose event")
})
return nil
}
func (c *Client) SendAsyncInitiation() error {
if c.VDomContextBlockId == "" {
return fmt.Errorf("no vdom context block id")
}
if c.GetIsDone() {
return fmt.Errorf("client is done")
}
return wshclient.VDomAsyncInitiationCommand(
c.RpcClient,
vdom.MakeAsyncInitiationRequest(c.RpcContext.BlockId),
&wshrpc.RpcOpts{Route: wshutil.MakeFeBlockRouteId(c.VDomContextBlockId)},
)
}
func (c *Client) SetAtomVals(m map[string]any) {
for k, v := range m {
c.Root.SetAtomVal(k, v, true)
}
}
func (c *Client) SetAtomVal(name string, val any) {
c.Root.SetAtomVal(name, val, true)
}
View on GitHub (pinned to a4447c1563)
Solutions
- Check client.GetIsDone() before calling SendAsyncInitiation and skip gracefully if done.
- Stop/tear down goroutines and timers in your shutdown handler (subscribe to the 'blockclose' event).
- Treat this error as a benign no-op in background workers rather than retrying.
- Create a new Client via Connect for a new block session instead of reusing the done client.
- Register cleanup in the On("blockclose") handler to cancel pending sends.
Example fix
// before
if err := client.SendAsyncInitiation(); err != nil { panic(err) }
// after
if client.GetIsDone() { return } // block closed; nothing to do
if err := client.SendAsyncInitiation(); err != nil { log.Printf("async initiation skipped: %v", err) } Defensive patterns
Strategy: type-guard
Validate before calling
if client.GetIsDone() {
return nil // block closed; skip silently
} Type guard
func canSend(c *waveapp.Client) bool {
return c.VDomContextBlockId != "" && !c.GetIsDone()
} Try / catch
if err := client.SendAsyncInitiation(); err != nil {
if strings.Contains(err.Error(), "client is done") {
return nil // benign: block already closed
}
return err
} Prevention
- Check GetIsDone() before any RPC after shutdown is possible.
- Cancel background goroutines/timers in the On("blockclose") handler.
- Never reuse a Client across block sessions; create a new one via Connect.
- Treat this error as a no-op signal, not a failure to retry.
When it happens
Trigger: Calling SendAsyncInitiation after the 'blockclose' event fired doShutdown, after the user closed the Wave block, or after an explicit shutdown/close call on the client.
Common situations: Background goroutines or timers still firing after block close; attempting to re-render on a client whose block was closed by the user; reusing a Client instance across block sessions.
Related errors
- no vdom context block id
- no job attached to controller
- error getting block: %w
- no shell input chan
- Layout model not found
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/e4a5d5ebac65b7ac.
Report an issue: GitHub.