charmbracelet/crush · error

failed to grant permission: status code %d

Error message

failed to grant permission: status code %d

What it means

GrantPermission expects the server to answer /workspaces/{id}/permissions/grant with HTTP 200. Any other status code (401, 403, 404, 500, etc.) produces this error. It indicates the server received and rejected the request, so the permission grant did not succeed.

Source

Thrown at internal/client/proto.go:687

	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 {
		return false, fmt.Errorf("failed to answer question batch: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Log/inspect the actual status code in the wrapped message and map it: 401/403 → re-authenticate, 404 → verify the workspace id, 5xx → check server logs.
  2. Confirm the workspace id passed to GrantPermission is current and valid.
  3. Refresh credentials or re-login if the status is 401/403.
  4. Check client/server version compatibility; upgrade the client if the endpoint contract changed.

Example fix

// before
resolved, err := client.GrantPermission(ctx, wsID, grant)
// after
resolved, err := client.GrantPermission(ctx, wsID, grant)
if err != nil && strings.Contains(err.Error(), "status code 404") {
	// refresh workspace id from server listing before retrying
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate workspace id shape and auth presence before calling
if wsID == "" || token == "" {
	return errors.New("workspace id and credentials are required before granting permissions")
}

Try / catch

resolved, err := client.GrantPermission(ctx, wsID, grant)
var statusErr *statusError // or inspect embedded "status code %d" text
if err != nil {
	switch {
	case strings.Contains(err.Error(), "status code 401"), strings.Contains(err.Error(), "status code 403"):
		return reauthenticateAndRetry(ctx, wsID, grant)
	case strings.Contains(err.Error(), "status code 404"):
		return fmt.Errorf("workspace %q not found: %w", wsID, err)
	default:
		return err
	}
}

Prevention

When it happens

Trigger: POSTing to /workspaces/{id}/permissions/grant when the workspace id does not exist (404), the caller lacks authorization (401/403), the request is malformed (400), or the server errors (5xx).

Common situations: Stale or wrong workspace id after a session restart; expired auth token/credentials; server version mismatch where the endpoint moved; server-side bug returning 500.

Related errors


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