charmbracelet/crush · error

failed to grant permission: %w

Error message

failed to grant permission: %w

What it means

GrantPermission POSTs a permission grant to /workspaces/{id}/permissions/grant. This error wraps any transport-level failure returned by c.post (DNS failure, connection refused, TLS error, request-encoding failure, context cancellation) before an HTTP response is available. It signals the grant request never completed at the HTTP layer, so the permission was not resolved by this client.

Source

Thrown at internal/client/proto.go:683

	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get sessions: status code %d", rsp.StatusCode)
	}
	var sessions []proto.Session
	if err := json.NewDecoder(rsp.Body).Decode(&sessions); err != nil {
		return nil, fmt.Errorf("failed to decode sessions: %w", err)
	}
	return sessions, nil
}

// GrantPermission grants a permission on a workspace. The returned
// bool reports whether this call resolved the pending request (true)
// or found it already resolved by a previous caller (false). A false
// value is not an error — it just means another subscriber resolved
// the same request first.
func (c *Client) GrantPermission(ctx context.Context, id string, req proto.PermissionGrant) (bool, error) {
	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/permissions/grant", id), nil, jsonBody(req), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {
		return false, fmt.Errorf("failed to grant permission: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return false, fmt.Errorf("failed to grant permission: status code %d", rsp.StatusCode)
	}
	var resp proto.PermissionGrantResponse
	if err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {
		return false, fmt.Errorf("failed to decode grant permission response: %w", err)
	}
	return resp.Resolved, nil
}

// AnswerQuestionBatch submits answers for a batch question on a
// workspace. Returns true if this call resolved the pending
// request, false if already resolved by another caller.
func (c *Client) AnswerQuestionBatch(ctx context.Context, id string, req proto.QuestionAnswer) (bool, error) {
	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/questions/answer", id), nil, jsonBody(req), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Verify the Crush server is running and reachable at the configured address (curl its health/base endpoint).
  2. Check and correct the client's base URL/port configuration used to build the Client.
  3. Retry GrantPermission; the grant may have been resolved by a previous caller (returns false, nil) so handle a false result as success.
  4. If timeouts recur, increase the context deadline or investigate network/proxy issues.

Example fix

// before
resolved, err := client.GrantPermission(ctx, wsID, grant)
if err != nil { log.Fatal(err) }
// after
resolved, err := client.GrantPermission(ctx, wsID, grant)
if err != nil {
	if errors.Is(err, context.DeadlineExceeded) {
		// server unreachable or too slow; retry or surface connectivity error
	}
	return fmt.Errorf("granting permission %s: %w", grant.ID, err)
}
if !resolved { /* already resolved by another subscriber; not an error */ }
Defensive patterns

Strategy: retry

Validate before calling

// Check server reachability before granting
func serverReachable(ctx context.Context, baseURL string) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
	if err != nil { return err }
	rsp, err := http.DefaultClient.Do(req)
	if err != nil { return err }
	defer rsp.Body.Close()
	return nil
}

Try / catch

resolved, err := client.GrantPermission(ctx, wsID, grant)
if err != nil {
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return err // propagate cancellation
	}
	// transient network failure: safe to retry (Resolved prevents double-grant)
	return retryGrant(ctx, wsID, grant, 3)
}
if !resolved { log.Info("already resolved by another subscriber") }

Prevention

When it happens

Trigger: Calling GrantPermission when the Crush server is unreachable, the workspace base URL/host is wrong, the network drops mid-request, or ctx is cancelled before the POST completes.

Common situations: Server process not running or restarted; misconfigured server address in client setup; firewall/proxy blocking the port; laptop offline or VPN down; request deadline exceeded while the caller waited too long.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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