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

  1. Check client.GetIsDone() before calling SendAsyncInitiation and skip gracefully if done.
  2. Stop/tear down goroutines and timers in your shutdown handler (subscribe to the 'blockclose' event).
  3. Treat this error as a benign no-op in background workers rather than retrying.
  4. Create a new Client via Connect for a new block session instead of reusing the done client.
  5. 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

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


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/e4a5d5ebac65b7ac. Report an issue: GitHub.