charmbracelet/crush · error

timeout waiting for agent: %w

Error message

timeout waiting for agent: %w

What it means

This error is wrapped by waitForAgent (internal/cmd/run.go:459) when the 30-second readiness timeout fires while the last GetAgentInfo call itself returned an error. It means the agent never reported IsReady within the deadline AND polling kept failing, so the wrapped error is the last polling failure (connection refused, HTTP error, etc.).

Source

Thrown at internal/cmd/run.go:469

		stop()
		return true, fmt.Errorf("agent error: %w", e.Payload.Error)
	}
	return false, nil
}

// waitForAgent polls GetAgentInfo until the agent is ready, with a
// timeout.
func waitForAgent(ctx context.Context, c *client.Client, wsID string) error {
	timeout := time.After(30 * time.Second)
	for {
		info, err := c.GetAgentInfo(ctx, wsID)
		if err == nil && info.IsReady {
			return nil
		}
		select {
		case <-timeout:
			if err != nil {
				return fmt.Errorf("timeout waiting for agent: %w", err)
			}
			return fmt.Errorf("timeout waiting for agent readiness")
		case <-ctx.Done():
			return ctx.Err()
		case <-time.After(200 * time.Millisecond):
		}
	}
}

// overrideModels resolves model strings and updates the workspace
// configuration via the server.
func overrideModels(
	ctx context.Context,
	c *client.Client,
	ws *proto.Workspace,
	largeModel, smallModel string,
) error {
	cfg, err := c.GetConfig(ctx, ws.ID)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Inspect the wrapped cause (%w) to see why GetAgentInfo fails (connection refused vs 401 vs 404) and fix that root issue
  2. Verify the server is running and reachable at the configured address before invoking crush run
  3. Check credentials/environment variables used by the client
  4. Increase the timeout or retry the run if startup is genuinely slow (resource-constrained environments)
  5. Confirm the workspace ID is valid

Example fix

// before
if err := runNonInteractive(...); err != nil {
	fmt.Println(err)
}
// after
if err := runNonInteractive(...); err != nil {
	var to *timeoutErr
	if errors.As(err, &to) {
		fmt.Printf("agent startup failed: %v\n", errors.Unwrap(err))
	}
}
Defensive patterns

Strategy: retry

Validate before calling

// Before invoking the run, check the server is reachable
conn, err := net.DialTimeout("tcp", serverAddr, 2*time.Second)
if err != nil {
	return fmt.Errorf("server unreachable at %s: %w", serverAddr, err)
}
conn.Close()

Try / catch

if err := runNonInteractive(...); err != nil {
	if strings.Contains(err.Error(), "timeout waiting for agent") {
		// log errors.Unwrap(err) for the last polling failure, then retry
	}
}

Prevention

When it happens

Trigger: The client.Run non-interactive path calls runNonInteractive -> waitForAgent; GetAgentInfo(ctx, wsID) returns an error on every poll (server unreachable, auth failure, workspace ID invalid) for the full 30s timeout window.

Common situations: Server not yet started or crashed during startup; wrong server URL/port in environment; expired or missing API credentials; pointing at a workspace that was deleted; slow machine/container startup exceeding 30s.

Understand the failure class

Related errors


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