pulumi/pulumi · error

creating organization role: %w

Error message

creating organization role: %w

What it means

This error wraps failures from POST /api/orgs/{org}/roles, which creates a new custom role. It is thrown by CreateOrgRole in the HTTP-state backend client when restCall fails — network errors, non-2xx status (401/403/409 for duplicates), or malformed server responses.

Source

Thrown at pkg/backend/httpstate/client/client.go:1930

	queryObj := struct {
		UXPurpose string `url:"uxPurpose,omitempty"`
	}{UXPurpose: uxPurpose}

	var resp apitype.ListRolesResponse
	if err := pc.restCall(ctx, "GET", path, queryObj, nil, &resp); err != nil {
		return nil, fmt.Errorf("listing organization roles: %w", err)
	}
	return resp.Roles, nil
}

// CreateOrgRole creates a new custom role in the given organization.
func (pc *Client) CreateOrgRole(
	ctx context.Context, orgName string, req apitype.CreateRoleRequest,
) (apitype.Role, error) {
	path := fmt.Sprintf("/api/orgs/%s/roles", url.PathEscape(orgName))
	var resp apitype.Role
	if err := pc.restCall(ctx, "POST", path, nil, &req, &resp); err != nil {
		return apitype.Role{}, fmt.Errorf("creating organization role: %w", err)
	}
	return resp, nil
}

// GetOrgRole fetches a single custom role by its identifier.
func (pc *Client) GetOrgRole(
	ctx context.Context, orgName, roleID string,
) (apitype.Role, error) {
	path := fmt.Sprintf("/api/orgs/%s/roles/%s",
		url.PathEscape(orgName), url.PathEscape(roleID))
	var resp apitype.Role
	if err := pc.restCall(ctx, "GET", path, nil, nil, &resp); err != nil {
		return apitype.Role{}, fmt.Errorf("getting organization role: %w", err)
	}
	return resp, nil
}

// UpdateOrgRole updates an existing custom role's name, description, and details.

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Verify the role name doesn't already exist (list roles first).
  2. Ensure the token has org admin/owner permissions.
  3. Confirm the org's subscription includes custom roles.
  4. Check the wrapped error's HTTP status: 409 → duplicate, 403 → permissions, 400 → invalid payload.

Example fix

// before
if err := pc.restCall(ctx, "POST", path, nil, &req, &resp); err != nil {
	return apitype.Role{}, fmt.Errorf("creating organization role: %w", err)
}
// after
var exists apitype.ListRolesResponse
if err := pc.restCall(ctx, "GET", fmt.Sprintf("/api/orgs/%s/roles", url.PathEscape(orgName)), nil, nil, &exists); err == nil {
	for _, r := range exists.Roles {
		if r.Name == req.Name { return apitype.Role{}, fmt.Errorf("role %q already exists", req.Name) }
	}
}
Defensive patterns

Strategy: validation

Validate before calling

func validRoleName(name string) bool {
	if name == "" || len(name) > 100 { return false }
	for _, r := range name {
		if !(r == '-' || unicode.IsLetter(r) || unicode.IsDigit(r)) { return false }
	}
	return true
}
// check existing roles before creating
existing, err := client.ListOrgRole(ctx, org)

Try / catch

if err := client.CreateOrgRole(ctx, org, req); err != nil {
	var apiErr *apitype.ErrorResponse
	if errors.As(err, &apiErr) && apiErr.Code == http.StatusConflict {
		return fmt.Errorf("role %q already exists", req.Name)
	}
	return err
}

Prevention

When it happens

Trigger: Calling CreateOrgRole with an org that lacks custom-roles entitlement, a role name that already exists (409), insufficient permissions, or an invalid CreateRoleRequest payload rejected by the server.

Common situations: Creating a role that already exists; non-admin token; org on a plan without custom roles; submitting role definitions with disallowed characters or missing fields.

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 pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/956eed21a20d2edb. Report an issue: GitHub.