charmbracelet/crush · error

failed to delete workspace: %w

Error message

failed to delete workspace: %w

What it means

DeleteWorkspace wraps an error from the underlying c.delete transport call for DELETE /workspaces/{id}?client_id=... . This is raised before status checking, so it reflects a transport-level failure: the request never completed successfully at the HTTP layer. The wrapped cause (connection error, canceled context, etc.) is available via errors.Is/As.

Source

Thrown at internal/client/proto.go:80

		return nil, fmt.Errorf("failed to get workspace: %w", err)
	}
	defer rsp.Body.Close()
	if err := checkStatus(rsp); err != nil {
		return nil, fmt.Errorf("failed to get workspace: %w", err)
	}
	var ws proto.Workspace
	if err := json.NewDecoder(rsp.Body).Decode(&ws); err != nil {
		return nil, fmt.Errorf("failed to decode workspace: %w", err)
	}
	return &ws, nil
}

// DeleteWorkspace deletes a workspace on the server.
func (c *Client) DeleteWorkspace(ctx context.Context, id string) error {
	q := url.Values{"client_id": []string{c.clientID}}
	rsp, err := c.delete(ctx, fmt.Sprintf("/workspaces/%s", id), q, nil)
	if err != nil {
		return fmt.Errorf("failed to delete workspace: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to delete workspace: status code %d", rsp.StatusCode)
	}
	return nil
}

// SetCurrentSession reports the client's current-session selection
// for the named workspace. An empty sessionID clears the entry. The
// request carries the process-scoped client ID minted in [NewClient]
// as a query parameter so the server can route the update to the
// correct [clientState] entry.
func (c *Client) SetCurrentSession(ctx context.Context, workspaceID, sessionID string) error {
	q := url.Values{"client_id": []string{c.clientID}}
	rsp, err := c.post(
		ctx,
		fmt.Sprintf("/workspaces/%s/current-session", workspaceID),

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check the server is running and reachable before deleting.
  2. Retry the delete with backoff; deletes are idempotent enough to tolerate re-issue (a 404 on retry means it succeeded).
  3. Confirm the server URL/port in the client configuration.
  4. Inspect errors.Is(err, context.Canceled) to avoid retrying caller-initiated cancels.

Example fix

// before
if err := client.DeleteWorkspace(ctx, id); err != nil { return err }
// after: retry transient transport errors
err := client.DeleteWorkspace(ctx, id)
for i := 0; err != nil && i < 3; i++ {
    if errors.Is(err, context.Canceled) { return err }
    time.Sleep(time.Duration(1<<i) * 100 * time.Millisecond)
    err = client.DeleteWorkspace(ctx, id)
}
Defensive patterns

Strategy: retry

Validate before calling

if !serverReachable(serverURL) { // e.g. net.DialTimeout probe
    return fmt.Errorf("skip delete: server unreachable")
}

Try / catch

err := client.DeleteWorkspace(ctx, id)
if err != nil {
    if errors.Is(err, context.Canceled) { return err }
    // transport error: retry once; a 404 on retry means success
    if rerr := client.DeleteWorkspace(ctx, id); rerr != nil && !strings.Contains(rerr.Error(), "404") {
        return rerr
    }
}

Prevention

When it happens

Trigger: Calling client.DeleteWorkspace(ctx, id) when the server is down or unreachable, the connection is reset mid-request, or the context is canceled before the DELETE completes.

Common situations: crush server stopped between listing and deleting a workspace; network blip on a laptop switching networks; context timeout too short for the delete; wrong server address after config change.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/4ac709bc4b80f3c6. Report an issue: GitHub.