Why does gs.hasRole() return different results in Background Script and ACL?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a week ago - last edited a week ago
Hello everyone,
I am trying to understand the behavior of gs.hasRole() in different execution contexts.
I am using the Australia release and have created a custom role with:
- Display Name: Intake Agent
- Internal Name: intake_agent
This role is assigned to the integration user that calls my inbound Scripted REST API.
In a Background Script, both of the following return true:
gs.hasRole("Intake Agent") -> true
gs.hasRole("intake_agent") -> true
However, in the ACL used for my Scripted REST API, I get different results.
gs.hasRole("Intake Agent") -> false
gs.hasRole("intake_agent") -> true
The same integration user is used in both tests, and I am not impersonating any user.
I would like to understand:
- Is this expected behavior?
- Does gs.hasRole() only work with the internal role name inside ACLs?
- Why does the display name work in a Background Script but not in the ACL?
- Is there any documentation explaining this behavior?
I'm looking to understand the platform behavior rather than just using the internal role name as a workaround.
Any guidance would be greatly appreciated. Thank you!
If this query of mine let you to discover something new please mark this as helfful.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a week ago
Hi @PurnaChandM ,
As a best practice, role names should follow a naming convention such as "intake_agent" . Using the role name consistently across the platform helps avoid confusion between the role's display label (for example, "Intake Agent") and its actual name ("intake_agent").
Could you please try using the role name and check whether it returns the same result in both the ACL and a Background Script?
Please mark helpful & correct answer if it's worthy for you.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a week ago
Hello @PurnaChandM , Firstly, I don't think there is anything called as 'Internal Name' in the Role context.
What you are observing is an expected behaviour since gs.hasRole() returns true for the admin users even if the Admin user doesn't have that role assigned. Hence, following ( gs.hasRole("intake_agent") -> true ) in the Background script returns true always even though this role doesn't exist and not assigned to Admin in the system, as Background script is executed manually using Admin account, I believe.
While, when used the same code inside the ACL, it returns false, as ACL is evaluated against the integration user, which doesn't have this role assigned.
If you'd like to verify the script part for the integration user, then you can include your Background "gs.info() " statements inside Scripted Rest API and should check the output.
Regards,
Nishant
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a week ago
Hello @PurnaChandM ,
Is this expected behavior? - Yes.
Does gs.hasRole() only work with the internal role name inside ACLs? - Yes
Why does the display name work in a Background Script but not in the ACL? - Generally, background script execution happens based on admin role that you have login. If you login as an admin and execute this script, it works.
Is there any documentation explaining this behavior?
Not sure but kindly check the below documents.
https://www.servicenow.com/docs/r/api-reference/rest-api-explorer/t_WbSvcRqACL.html
https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB3067343
***************************************************************************************************************************************
If you found this post helpful, please mark it as “Helpful.”
Feel free to share your insights or suggest corrections to benefit our community.
Regards
Murali.K.V.R
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a week ago - last edited Sunday
Hello
good question, and it's a subtle one. I reproduced it in two PDIs, including one on the Australia release to match yours, and the behavior is consistent across both.
What I tested
I created a role named exactly "Intake Agent" (space, title case). Show XML confirms <name>Intake Agent</name> with an empty suffix. I assigned it to a non-admin user and checked in that user's context:
getRoles() → ...,intake agent
hasRole("intake_agent") → false
hasRole("Intake Agent") → true
What that tells us
getRoles() returns the role name lowercased but keeps the space — spaces are not converted to underscores.
hasRole() compares case-insensitively, but treats space and underscore as different characters. So "Intake Agent" matches and "intake_agent" does not.
In other words, hasRole() isn't resolving a display name to an internal one. It lowercases your argument and matches it literally against the role's stored name.
Reconciling your setup
Here's the part worth clarifying. Your original post lists the internal name as intake_agent, but your later reply says that value isn't actually stored on the role and you're unsure where it comes from. Those describe two different setups, and only one check settles it — right-click the role header → Show XML and read the <name> tag:
If <name> is intake_agent (underscore), that's simply your stored role name. Nothing is being derived or generated — intake_agent matching is expected, and "Intake Agent" is just a label you're reading elsewhere.
If <name> is Intake Agent (space), then based on my tests you'd see the reverse of what you're reporting, so it'd be worth re-confirming which hasRole() call actually returns true in the ACL.
My money is on the first — you most likely set name = intake_agent at creation, and "Intake Agent" is the display value. That fully explains every result you're seeing.
Background Script vs ACL
Both calls returning true in your Background Script is unrelated to the role name. Background Scripts run as admin, and hasRole() returns true for an admin regardless of the argument — even a role that doesn't exist. Your ACL runs as the non-admin integration user, so there's no admin short-circuit, and you get the real, literal comparison. That's why the ACL is the context showing you the true behavior — not a special ACL-only rule.
Best practice
Name roles lowercase_with_underscores from the start so the stored name and every hasRole() / ACL check line up exactly, with no display-vs-name ambiguity.
Screenshots from my Australia PDI attached for reference.
Can you please mark Helpful @PurnaChandM , if my research helped you anyway please