The goal is to synchronize issues from 2 projects from 2 private JIRA servers. One can not see the other, but each one can have access to the Internet.

One of the possible option I could think of is to use an intermediate site, which could be a JIRA Cloud  instance, accessible by both private JIRA, and using Exalate to synchronize issues among them:

  • Issue created Server A, meeting certain criteria, will be sent to Cloud instance, and create a mirror issue here
  • Then Cloud instance sees that this newly created issue has certain criteria that needs to be sent to server B will sent the information to server B to create a peer issue there
  • Update from A or B will be sent to Cloud instance and redirected to the other.

In reality configuration, the above steps don't work, since in Step 2, Exalate in the Cloud instance saw that the issue is newly created by Exalate itself, so it will ignore its own change, and doesn't send the request to server B

Is there any work around for this? For ex:

  • If we can say that the proxy user for the 1st connection is proxy1, and for 2nd connection is proxy2, the issue created in Cloud by proxy1 can still be handled subsequently by proxy2 as a different user, hence allowed to be sent to server B
  • Or we can have some way to tell Exalate that the 2 connections are different so can be relayed the requests just fine.

Do you have any advice on this or any other option to achieve the goal?

    CommentAdd your comment...

    2 answers


      It took a while to resolve this one.  The script listener API of Script Runner for Jira Cloud can be improved.  If you want to have the details of the research check the Atlassian development community article here.

      It boils down to testing if the event is a jira:issue_updated event, and then test that the update has been performed by Exalate (using the available User object).  All other events can pass as they will not create the dreaded loop.

      The snippet has been updated


      Let me know how it goes

      1. Hung Nguyen

        Hi Francis,

        I have successfully tested the simple connection with 2 separate listeners, one for Comments and another for Issue Created/Updated. And things seemed to work.

        I will need to try your suggestion too to consolidate the listeners. But it least the solution works and I have demo-ed to the team about this. I think they like it.

        Of course, if Exalate can have something to simplify this then it would be even better.

        Thanks for your help.

      2. user-3fd1a

        We are thinking of delivering a docker image containing the logic for configuring multiple 'channels', each channel acting as a tunnel to connect a pair of nodes.  

        The container can be deployed anywhere where 2 instances can meet.

      3. Hung Nguyen

        Hi Francis,

        After we tested more on this, there are some unexpected results occurred from this setup:

        • If both end-sides are updating on different fields of the same issues (corresponding issue for that site), then the middle site will eventually clear out the updated field by the non-updated value from the other side
      4. Hung Nguyen


        This approach has shown a loop back to the originator site and caused error. 

        A does a transition from Status 1 to Status 2, sync-ing to B to do the same thing.

        While it's doing it, A continues to transition from Status 2 to Status 3,

        But at that time B is just finishing the first transition from Status 1 to Status 2, it triggers a Time update  by Script runner, which then send back Status 2 back to A, forcing A to move back from Status 3 to Status 2. Which is NOT what we want

      5. Francis Martens (iDalko)

        Back to the drawing board.

        Need to do some head-scratching there.

      6. Francis Martens (iDalko)

        Sorry for the delay here.  We have been had a number of debates how to solve this one.  It seems we have something workable, which still needs to be spiked out.

        Hopefully, I can come back to it in the course of next week.

      7. Hung Nguyen

        We are looking forward to a solution for this problem. 

        Right now, I had to temporarily keep a flag to say which side is changing the status last. Then prevent that side from updating the Status by exalate.

        I hope that you can have a more fool-proof solution.

      8. Francis Martens (iDalko)

        It is the same concept but then based on the change history.

        The replica has a time stamp, and you can test if the field changed.

        We are delivering an externalised script with a method ifNoConflict 

        to be used as

        ifNoConflict(issue, replica,"status") {
            // put your status logic here

        Would this fit?

      9. Hung Nguyen

        Hello Francis,

        I am not sure I understand the solution here.

        What status logic should I put into the function. And how the function is used?

      10. Francis Martens (iDalko)

        Hung Nguyen

        I published a small piece regarding conflict handling on the documentation site
        'How to avoid conflicts'  This conflict avoidance approach is based on an update map which is calculated from the change history.

        To port this solution to your specific situation, additional steps need to be taken, where the change history needs to be copied over from one side to the other.

        A POC has been established.  Please reach out at your convenience, and we can discuss the details during a demo.

      11. Hung Nguyen

        Yes, I'd like to see how the PoC works and how we can incorporate that into our case.

        Please let us know what would be the next steps?

      12. Hung Nguyen

        Just for the sake of others who need the similar solution, Exalate has helped to configure such a 3-node system to synchronize 2 private Jira instances. So if you need this type of setup, Exalate should be a good choice

      13. Francis Martens (iDalko)

        Thanks Hung Nguyen

        We are currently working on the exalate gateway which will allow to connect two private instances in a secure way.  The Jira cloud approach has a couple drawbacks which will be addressed in the exalate gateway. If interested - send a mail to earlyaccess@exalate.com for getting more information on the feature.  We are looking for early adopters to kick the tires.

      CommentAdd your comment...

      Hi Hung Nguyen

      Thanks for asking!

      Can you check the triggers?  In the example configuration below, there are 2 triggers to feed the 2 connections.  This ensure that whenever an issue is created on the project SCRUM A, that issue ripples through to project ANTWERP, which ripples through to project SCRUM B

      1. Hung Nguyen

        Yes. I knew that. And the triggers work well when I manually update something directly on the clone issue in the middle site.

        The requirement is Site A will sent to site B if the Component field is compB. And in the middle Cloud site, we also set the Component of this peer issue to be compB. And we had the trigger that if Component = B, sent it to Site B

        When an issue is created in Site A with component = compB, the trigger from A fired up and created a peer issue in middle site C, with component = compB

        But the trigger in middle site C does NOT take this issue to send to Site B as expected.

        If I modify a comment of this issue in site C, then the trigger starts to fire and creates issue to Site B, meaning that the trigger does work

      2. Hung Nguyen

        Please note that in my middle site, all the issues from both sites are put into the only project, namely P, but with different components. And the triggers 1 will take only component compA to send to A, and compB to create/update to B. 

      3. user-3fd1a

        Hmm - back to the drawing board then - the trigger setup is on a single instance, so there might be something wrong on the cloud connector.

        I will try it for myself and circle back

      4. Hung Nguyen

        The triggers on the middle Cloud site are fired only from a manual update to the issues in its site. They ignore all the updates collected by Exalate from either remote sites. I understand that this is to prevent to send back to the original remote site again its own changes. But in this case, we need to relay the changes instead. So if each connection use a different proxy user from the other, then maybe it works

      5. Hung Nguyen

        Do you have any suggestion Francis?

        We are longing for a solution on this. 



      6. Hung Nguyen


        Is there any update on this issue? We want to see if there is any work around for this. Or should abandon the plan of using this?



      7. user-3fd1a

        Hi Hung,

        Thanks for pinging me frequently -  the discussion got off my radar.

        Picking it up again

      8. user-3fd1a

        Your analysis is spot on, so we found a workaround (until we can integrate something in the product which does this correctly)

        Like always -  script runner to the rescue.

        1. Add ScriptRunner for Jira to your cloud environment
        2. Create a custom field 'SyncTrigger' and associate to the project
        3. Create a ScriptListener in Script runner  and set following fields
          1. Name of the listener: priv-priv
          2. Events - Issue created, updated, commented, worklog ...  whatever you  will be updating
          3. As this user: ScriptRunner add-on user
          4. empty
          5. Test if the exalate user has done the update  - else you get a loop
          6. Script listener code itself

            Is available here

        4. Give it a try and let us know
      9. Hung Nguyen

        How can I do step e. Test if the exalate user has done the update ? 

        I read all the exalate document but could not see where we can get this testing.

        Can you help with it too?


      10. Hung Nguyen

        I put step e. as 1==1 just to make it passed. But the script didn't execute, even though the condition seemed to execute passed. The execution history is always empty. I'm not sure if it's because the script ?

      11. Hung Nguyen

        On the history screen shot, you can see the top condition ran, but not the script content at bottom.

      12. Hung Nguyen

        After several testing, I saw that it's the following test that was evaluated to false 

        user.displayName == 'Exalate'

        in the condition

        And if we put this in the script content, then Scriptrunner complain that user object can not be valid

      13. user-3fd1a

        ugh - strange

        When you expand the script context - user is one of the available script context objects - isn't it?


        It took me a couple of iterations to get it right, the script behind the link


        should work. 

        No need to add a condition, as the  test in the begin of the script is avoiding the  loop which I encountered when testing

      14. Hung Nguyen

        This is the exact error message in the history event, when I put the if (user.displayName = "Exalate")

        at the top of the script:

        2019-09-19 20:59:51.786 ERROR - No such property: user for class: Script1 on line 1
        2019-09-19 20:59:52.607 ERROR - Class: com.adaptavist.sr.cloud.events.WebhookExecution, Config: null
      15. Hung Nguyen

        OK, I looked closer into the ScriptRunner document. Only Issue Created and Issue Updated got 'user' in context. For Comment created and Comment Updated event, there is no user. Surprise!

        Now the workaround looks a bit uglier. And for Attachment Created event, there is even NO issue context. Not sure how can update issue field in that case.

      16. user-3fd1a

        Ah - I understand - I will investigate and come back to it.

      17. user-3fd1a

        Hung Nguyen - looking forward to your feedback

      CommentAdd your comment...