I work for Exalate, the company that builds the integration app referenced below.
Most comment sync issues in a Jira integration between two Jira Service Management projects are not sync issues at all. They are visibility issues, and they usually surface after something reached a customer that should not have.
I spend a lot of my week inside other people's connections. Four scenarios account for the overwhelming majority of what I see. Each presents differently, and three of the four get misdiagnosed as product bugs when they are actually poor configuration decisions being made.
Symptom: An agent writes an internal note. It syncs to the other project and shows up as a public reply the customer can read.
What people assume: The integration ignored the internal flag.
What is actually happening: It’s not configured to carry the flag. In JSM, public and internal are a native distinction, but that distinction is a property of the comment in that project. What the comment becomes when it crosses into a second project is a decision. If you did not make the decision, you inherited a default.
The fix is a filter on the outgoing side, so internal notes never leave in the first place:
// Outgoing script
replica.comments = issue.comments.findAll { !it.internal }
!it.internal reads as "not internal." Public comments pass; internal notes are dropped from the replica before transmission.
The above script is the outgoing side of the Jira instance, which filters internal comments. It’s built using Exalate, a script-based integration solution.
Note the prerequisite that catches people out: the issue needs a customer request type assigned, or the internal flag will not behave predictably to begin with.
Ask yourself first: should internal notes leave at all? Filtering on the way out is safer than relabelling on the way in, because a comment that never leaves cannot be misclassified downstream by the scripts on the other side.
Symptom: Comments flow in from the partner project, and every one of them lands on the portal. Your customers are reading a vendor's engineering chatter.
What is actually happening: This is the first scenario seen from the other end of the pipe, and it is the more dangerous version, because you do not control the sending side. If your partner edits their outgoing script, your exposure changes with it and you’re unaware.
The fix is to stop trusting the sender. Force the classification on arrival:
// Incoming script
issue.comments = commentHelper.mergeComments(issue, replica, { it.internal = true })
mergeComments reconciles incoming comments with what already exists on the request. The closure is the third argument and runs per comment as it merges. it.internal = true forces every one of them to land as an internal note.
A default worth knowing while you are in here: called with no closure, mergeComments prepends the original author's name to each comment. If you want the raw body, pass { it } instead.
The principle: in a cross-organizational connection, each side enforces its own visibility policy on incoming data rather than relying on the other side's outgoing filter. Two independent controls, not one shared assumption.
Symptom: Comments restricted to a Jira project role never appear on the other side. No error. No partial sync. They behave as though they do not exist.
What people assume: The script is wrong.
What is actually happening: The script never sees the comment. Role restriction means only members of that role can read it, and the integration's proxy user is subject to that rule like any other account. The comment is not being filtered out. It is invisible.
Here is the part that costs people a day: full comment permissions in the permission scheme are not enough. Permission scheme access and project role membership are different things. The proxy user has to be an actual member of the restricted role.
The fix: add the proxy user to the role. On Jira Cloud, you cannot do this through the UI, because Jira Cloud does not surface app accounts in the project role picker. You add it via the REST API using the account ID.
If you are debugging this, test the hypothesis before touching any code. Restrict a throwaway comment to a role the proxy user definitely does belong to and see whether it syncs. If it does, you have confirmed a permissions gap rather than a logic error.
No, and treating them as one control is the fourth scenario.
Symptom: Scripts that work perfectly for internal notes do nothing for role-restricted comments, and usually it’s the part that’s difficult to decode.
What is actually happening: Two unrelated mechanisms that both loosely mean "not everyone sees this."
|
JSM internal flag |
Jira project role restriction |
|
|
Belongs to |
Jira Service Management |
Jira platform permissions |
|
Controls |
Customer portal visibility |
Which Jira users can read it |
|
Exposed on the comment object |
Yes |
Not directly, on Cloud |
|
How you handle it in sync rules |
Read or set it.internal |
Fetch visibility via REST API |
|
Typical symptom when wrong |
Wrong classification |
Comment invisible entirely |
For role restrictions on Cloud, you retrieve each comment's visibility information with httpClient() against the Jira REST API, then apply filtering logic yourself.
There is no boolean to read off the comment. The Jira Service Management API exposes the internal flag directly; the platform-level Jira REST API is what you need for role visibility, which is why the two failures need different tooling to diagnose.
Atlassian references if you are building this:
All four scenarios trace back to an issue that should ideally be solved at design time: who owns the visibility decision, the sending project or the receiving one?
Pick one and be consistent.
Sender owns it. The outgoing script filters, and whatever arrives is assumed safe. Simpler, fewer moving parts, appropriate when both projects sit inside one organization, and one team maintains both configurations.
Receiver owns it. The incoming script classifies everything on arrival regardless of what was sent. More work, but the only defensible model across an organizational boundary, because it survives the other side changing their configuration without telling you.
A large share of the incidents I look at come down to a connection where one side assumed the first model and the other side assumed the second.
The incoming one.
Whatever you build, exercise it against real data before it reaches production. Exalate's Test Run previews what a script would do without writing to the target.
Weigh your testing toward the incoming rules.
An outgoing mistake usually means a comment fails to arrive.
An incoming mistake means a comment arrives with the wrong visibility and higher chances of a customer reading it.
Only one of those is recoverable.
Four scenarios, four distinct causes.
Internal notes leaking outward is a missing outgoing filter.
Everything arriving public is a missing incoming classification.
Role-restricted comments vanishing entirely is proxy user role membership, not scripting.
Scripts that work for one and not the other mean the internal flag and project role restrictions are being handled as one mechanism when they are two.
The design decision underneath all of them is who owns visibility, the sender or the receiver. Answer that before writing the first line of a sync rule.
The app referenced here (Exalate) is on the Atlassian Marketplace:
If you are seeing a fifth scenario I have not listed, post the symptom in the comments. I am interested in the ones that do not fit these four.