charmbracelet/crush · error · ErrNotFound

not found

Error message

not found

What it means

ErrNotFound reports that the server answered 404. For a workspace-scoped call this means the server no longer knows the workspace — it was torn down, or the server was replaced under the client — so retrying the same ID can never start succeeding again. The client must re-register (create a new workspace) rather than retry.

Source

Thrown at internal/client/errors.go:18

package client

import (
	"errors"
	"fmt"
	"net/http"
	"slices"
)

// Typed outcomes callers need to distinguish from ordinary transport
// failures. Match them with errors.Is.
var (
	// ErrNotFound reports that the server answered 404. For a
	// workspace-scoped call this means the server no longer knows the
	// workspace — it was torn down, or the server was replaced under the
	// client — so the right response is to re-register rather than retry
	// the same ID, which can never start succeeding again.
	ErrNotFound = errors.New("not found")

	// ErrServerBusy reports that the server declined to shut down
	// because it is still hosting workspaces or is midway through
	// creating one. A client asking a version-mismatched server to stand
	// down must keep using it instead of assuming it is going away.
	ErrServerBusy = errors.New("server busy")

	// ErrServerShuttingDown reports that the server refused the request
	// because it has already committed to exiting. The work is not lost:
	// a replacement server can be started and the request retried
	// against it.
	ErrServerShuttingDown = errors.New("server is shutting down")

	// ErrUnsupported reports that the running server does not understand
	// the request because it predates the feature. Callers must decide
	// what is safe to do with an older server rather than treating the
	// failure as transient.
	ErrUnsupported = errors.New("unsupported by the running server")

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Re-register: create a new workspace instead of retrying the stale ID
  2. Match with errors.Is(err, client.ErrNotFound) and branch to re-registration logic
  3. Check whether the server process was replaced and reconnect to the current one

Example fix

// before
if err := client.SendMessage(ctx, wsID, msg); err != nil { return err }
// after
if err := client.SendMessage(ctx, wsID, msg); err != nil {
    if errors.Is(err, client.ErrNotFound) {
        wsID, err = client.CreateWorkspace(ctx, path)
        if err != nil { return err }
        return client.SendMessage(ctx, wsID, msg)
    }
    return err
}
Defensive patterns

Strategy: fallback

Type guard

func isErrNotFound(err error) bool {
    return errors.Is(err, client.ErrNotFound)
}

Try / catch

if err := client.SendMessage(ctx, wsID, msg); err != nil {
    if errors.Is(err, client.ErrNotFound) {
        return reRegisterAndResend(ctx, path, msg) // never retry the old ID
    }
    return err
}

Prevention

When it happens

Trigger: RetireClient receiving 404; checkStatus mapping http.StatusNotFound to this sentinel for any workspace-scoped call (SendMessage, attach, session ops); runSubscription's stream endpoint vanishing; AgentReadyErr surfacing a 404 while polling agent readiness.

Common situations: The server's idle shutdown replaced the process; detach grace expired and the workspace was torn down; workspace-scoped call issued against a restarted server that lost in-memory state.

Related errors


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