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
- Verify the Crush server is running and reachable at the configured address (curl its health/base endpoint).
- Check and correct the client's base URL/port configuration used to build the Client.
- Retry GrantPermission; the grant may have been resolved by a previous caller (returns false, nil) so handle a false result as success.
- 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
- Health-check the server before issuing grants
- Use a context with a sane timeout (e.g. 10s) instead of one that expires mid-request
- Treat a false Resolved return as success — never retry-loop on it
- Keep client base URL/port in one config location to avoid drift
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
- failed to set permissions skip requests: %w
- failed to make request: %w
- failed to refresh OAuth token: %w
- failed to check project init: %w
- failed to mark project initialized: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/745223fb95fe70e7.
Report an issue: GitHub.