Masking of source project names in Outgoing Sync script

We have a specific business requirement for our Jira-to-Jira integration using the Exalate plugin, Whenever an issue is synced from the Source instance to our Destination instance, we capture the source project’s name using replica.project?.name. This value is populated into a custom Select List field named “Source” on the destination side.

For security and privacy reasons, we need to mask certain source project names. Instead of displaying their real names in the destination “Source” field, we want them to appear as “Roomed”.

  • If the source project is on our “mask list”, the destination field should be set to “Roomed”.

  • If the source project is not on that list, it should continue to display its real project name as it does currently.

    is it possible to implement this requirement by making changes only to the Outgoing Sync script in the Source Jira instance, without requiring any modifications to the Incoming Sync script on the Remote Jira side?

You can achieve this masking requirement by modifying only the Outgoing Sync script on the Source Jira instance. In your Outgoing Sync script, you can check if the project name is in your “mask list” and set a masked value (like “Roomed”) in the replica. The Incoming Sync script on the destination side will then receive the masked value and populate it into the custom field as usual—no changes needed there.

Here’s how you can do it:

  1. Define your mask list in the Outgoing Sync script.
  2. When setting the value for the custom field in the replica, check if the project name is in the mask list.
  3. If it is, set the value to “Roomed”; otherwise, use the real project name.

Example snippet for your Outgoing Sync script:

def maskList = ["Project A", "Project B", "Sensitive Project"] // Add your project names here

def sourceProjectName = issue.project?.name
if (maskList.contains(sourceProjectName)) {
    replica.customFields."Source" = "Roomed"
} else {
    replica.customFields."Source" = sourceProjectName
}

This way, the masking logic is handled entirely on the source side, and the destination will always receive either the real project name or “Roomed” as appropriate. For more details on scripting in Exalate, check the documentation:

Let me know if you need help with more advanced masking logic or have other requirements!

Hi @pramak1X ,

Thank you for posting your use case on the Exalate Community Portal.

I’ll get back to you by tomorrow after testing the code snippet I have.

Kind regards,
Ashar

Thanks, Ashar. Please keep me posted on any updates.

Hi @pramak1X ,

I have worked on two requirements about hiding source project identity when syncing issues from Source Jira to Destination Jira.

Case 1: Hide project key in the source issue link

We wanted a clickable link to the source issue, but without showing the source project key in the label.

Fix: use a text field with Wiki Style Renderer, and build the link with a fixed label instead of the key.

// Incoming rule, destination side
def SOURCE_BASE = "https://<source-instance-url>"
def LABEL = "Source issue"

if (replica.key) {
    issue.customFields."RemoteIssueKey".value =
        "[" + LABEL + "|" + SOURCE_BASE + "/browse/" + replica.key + "]"
}

Field must be multi-line text with renderer set to Wiki Style Renderer, or it will show raw text instead of a link.

Note: this only hides the key from the label. The real key is still visible on hover, in REST API response, and in exports. If you need it fully hidden everywhere, that needs a separate redirect service, not just a script.

Case 2: Hide source project name/key completely

We wanted a Select List field on destination to show the source project, but some projects should show as “ROOMED” instead of their real name.

Fix: do the masking on the outgoing script (source side), before data is sent. This way destination never receives the real value.

// Outgoing rule, source side
replica.project = issue.project

def MASK_LIST = ["<project-key-1>", "<project-key-2>"]
def MASK_LABEL = "ROOMED"

if (issue.project?.key in MASK_LIST) {
    replica.project.key = MASK_LABEL
    replica.project.name = MASK_LABEL
}
// Incoming rule, destination side
if (replica.project?.key) {
    issue.customFields."Source".value = replica.project.key
}

Important: “Source” is a Select List field, so every value it can receive (all real project keys + “ROOMED”) must already exist as an option, or it will fail silently.

Note: unlike Case 1, this is a full mask. Real project key never leaves the source, so it cannot be seen even in REST or export on destination side.

Summary

Case 1: issue link Case 2: project identity
Masked where Destination side (display only) Source side (before sending)
Real value recoverable? Yes, via hover/REST/export No, never sent

Both tested on demo Jira cloud-JiraDC connection with fresh issues, masked and unmasked, confirmed via UI and REST. However, it should also work between two JiraDC instances.

Kind regards,

Ashar

Hi Ashar,
We tried this logic but it is not working and showing Waiting for Remote.

getting below error in logs in Incoming Side.

com.exalate.api.exception.script.IssueTrackerScriptException: Failed to load external script: No signature of method: Script1.syncSource() is applicable for argument types: (com.exalate.basic.domain.hubobject.v1.BasicHubIssue, com.exalate.basic.domain.hubobject.v1.BasicHubIssue) values: [[], []] at com.exalate.error.services.ScriptExceptionCategoryService.$anonfun$categorizeProcessorAndIssueTrackerExceptionsIntoScriptExceptions$1(ScriptExceptionCategoryService.scala:43) at com.exalate.error.services.ScriptExceptionCategoryService.$anonfun$categorizeProcessorAndIssueTrackerExceptionsIntoScriptExceptions$1$adapted(ScriptExceptionCategoryService.scala:38) at scala.collection.immutable.List.foreach(List.scala:333) at com.exalate.error.services.ScriptExceptionCategoryService.categorizeProcessorAndIssueTrackerExceptionsIntoScriptExceptions(ScriptExceptionCategoryService.scala:38) at com.exalate.processor.ExalateProcessor.executeProcessor(ExalateProcessor.java:77) at com.exalate.processor.jira.JiraCreateIssueProcessor.executeCreateProcessor(JiraCreateIssueProcessor.java:336) at com.exalate.processor.jira.JiraCreateIssueProcessor.createIssue(JiraCreateIssueProcessor.java:189) at com.exalate.replication.request.CreateIssueSyncRequestState.transition(CreateIssueSyncRequestState.java:81) at com.exalate.replication.request.CreateIssueSyncRequestState.transition(CreateIssueSyncRequestState.java:28) at com.exalate.replication.in.RequestProcessorService.processSyncRequest(RequestProcessorService.java:400) at com.exalate.replication.in.RequestProcessorService.processSyncRequestsForIssue(RequestProcessorService.java:273) at com.exalate.replication.in.RequestProcessorService.processRequestsByTwinTraceInternal(RequestProcessorService.java:217) at com.exalate.replication.in.RequestProcessorService.processRequestsByTwinTrace(RequestProcessorService.java:181) at com.exalate.replication.SyncWorkerService.processInQueueForAssignment(SyncWorkerService.java:149) at com.exalate.replication.SyncWorkerService.processInQueueIfAssigned(SyncWorkerService.java:86) at com.exalate.replication.SyncWorkerService.doSyncWork(SyncWorkerService.java:50) at com.exalate.replication.akka.SyncWorkerActor.processAllAssignmentsForWorker(SyncWorkerActor.java:150) at com.exalate.replication.akka.SyncWorkerActor.onReceive(SyncWorkerActor.java:106) at akka.actor.UntypedAbstractActor$$anonfun$receive$1.applyOrElse(AbstractActor.scala:332) at akka.actor.Actor.aroundReceive(Actor.scala:537) at akka.actor.Actor.aroundReceive$(Actor.scala:535) at akka.actor.AbstractActor.aroundReceive(AbstractActor.scala:220) at akka.actor.ActorCell.receiveMessage(ActorCell.scala:579) at akka.actor.ActorCell.invoke(ActorCell.scala:547) at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270) at akka.dispatch.Mailbox.run(Mailbox.scala:231) at akka.dispatch.Mailbox.exec(Mailbox.scala:243) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) Caused by: javax.script.ScriptException: javax.script.ScriptException: com.exalate.api.exception.IssueTrackerException: Failed to load external script: No signature of method: Script1.syncSource() is applicable for argument types: (com.exalate.basic.domain.hubobject.v1.BasicHubIssue, com.exalate.basic.domain.hubobject.v1.BasicHubIssue) values: [[], []] at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:164) at java.scripting/javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:262) at com.exalate.processor.ExalateProcessor.execute(ExalateProcessor.java:114) at com.exalate.processor.ExalateProcessor.executeProcessor(ExalateProcessor.java:75) … 27 more Caused by: javax.script.ScriptException: com.exalate.api.exception.IssueTrackerException: Failed to load external script: No signature of method: Script1.syncSource() is applicable for argument types: (com.exalate.basic.domain.hubobject.v1.BasicHubIssue, com.exalate.basic.domain.hubobject.v1.BasicHubIssue) values: [[], []] at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:334) at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:161) … 30 more Caused by: com.exalate.api.exception.IssueTrackerException: Failed to load external script: No signature of method: Script1.syncSource() is applicable for argument types: (com.exalate.basic.domain.hubobject.v1.BasicHubIssue, com.exalate.basic.domain.hubobject.v1.BasicHubIssue) values: [[], []] at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321) at Script7.run(Script7.groovy:26) at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:331) … 31 more Caused by: groovy.lang.MissingMethodException: No signature of method: Script1.syncSource() is applicable for argument types: (com.exalate.basic.domain.hubobject.v1.BasicHubIssue, com.exalate.basic.domain.hubobject.v1.BasicHubIssue) values: [[], []] at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:72) at org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.unwrap(IndyGuardsFiltersAndSignatures.java:163) at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321) at Script1.executeSync(Script1.groovy:207) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328) at groovy.lang.MetaClassImpl.doInvokeMethod(MetaClassImpl.java:1334) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1089) at groovy.lang.MetaClassImpl.invokeMethodClosure(MetaClassImpl.java:1018) at groovy.lang.MetaClassImpl.doInvokeMethod(MetaClassImpl.java:1208) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1089) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1008) at groovy.lang.Closure.call(Closure.java:433) at groovy.lang.Closure.call(Closure.java:422) at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321) at Script7.run(Script7.groovy:23) … 32 more

/*

def syncSource(issue, replica) {

def projectName = replica.project?.name

if (projectName) {

    def option = nodeHelper.getOption(issue, "Source", projectName)

    if (option) {

        issue.customFields."Source".value = option

        log.debug("Set 'Source' field to project name: '${projectName}'")

    } else {

        log.warn("Could not find or create option '${projectName}' for 'Source'")

    }

}

}

*/

/*def syncSource(issue, replica) {

// Use project key from replica

def projectKey = replica.project?.key

def projectName = replica.project?.name



if (projectKey == 'AMRI') {

    def option = nodeHelper.getOption(issue, "Source", "Roomed")

    if (option) {

        issue.customFields."Source".value = option

        log.debug("Set 'Source' field to 'Roomed' for project key: 'AMRI'")

    } else {

        log.warn("Could not find or create option 'Roomed' for 'Source'")

    }

} else if (projectName) {

    def option = nodeHelper.getOption(issue, "Source", projectName)

    if (option) {

        issue.customFields."Source".value = option

        log.debug("Set 'Source' field to project name: '${projectName}'")

    } else {

        log.warn("Could not find or create option '${projectName}' for 'Source'")

    }

}

}

*/

it is working previously with the above changes made only in remote side. We have commented and have added only this below line and now it is not working.

if (replica.project?.key) {

issue.customFields."Source".value = replica.project.key

}

Whether this logic added in incoming script will work. if the Project key mentioned in Outgoing is not matching then it should display original project name in the Source field. if it matches then only it should show Roomed Option in remote.

if (replica.project?.key) {

issue.customFields."Source".value = replica.project.key

}

Hi Ashar,

Any updates? Please revert back as early as possible.

Hi @pramak1X ,

Could you please share the entire Outgoing/Incoming script rules with the code lines included that generated an error, and failed for you?

Kind regards,

Ashar

Hi Ashar,

Please find the requested details.

Outgoing Script

replica.key            = issue.key
replica.assignee       = issue.assignee
replica.reporter       = issue.reporter
replica.summary        = issue.summary       //ok
replica.description    = issue.description   //ok
replica.comments       = issue.comments.findAll {it.internal}
replica.resolution     = issue.resolution
replica.status         = issue.status        //ok
replica.parentId       = issue.parentId
replica.priority       = issue.priority  //ok
replica.attachments    = issue.attachments   //ok

replica.project        = issue.project      //ok
def MASK_LIST = ["AMRI"]
def MASK_LABEL = "ROOMED"
if (issue.project?.key in MASK_LIST) {
    replica.project.key = MASK_LABEL
    replica.project.name = MASK_LABEL
}

replica.customFields."Service type" = issue.customFields."Service type"

replica.created = issue.created
// Outgoing sync script

def priorityName = replica.priority?.name

// Outgoing sync script for components
replica.components = issue.components

// Outgoing sync script for affected versions
replica.affectedVersions = issue.affectedVersions

// Outgoing sync script for fix versions
replica.fixVersions = issue.fixVersions

// Outgoing sync script for Requested Resolution Date
replica.customFields."Requested Resolution Date" = issue.customFields."Requested Resolution Date"

Incoming Script

import java.sql.Timestamp
import java.time.LocalDate
import java.time.LocalDateTime
def getDefaultUser() {
    return nodeHelper.getUserByEmail("sys_dejsm@intel.com")
}
def getComponentLeadUser(componentName, projectKey) {
    def project = nodeHelper.getProject(projectKey)
    def component = nodeHelper.getComponent(componentName, project)
    if (component?.lead) {
        // Prefer email if available
        if (component.lead.email) {
            return nodeHelper.getUserByEmail(component.lead.email)
        }
        // Fallback to username/accountId
        if (component.lead.name) {
            return nodeHelper.getUser(component.lead.name)
        }
    }
    return null
}
def setIssueTypeBasedOnServiceType(issue, replica) {
    def serviceTypeValue = replica.customFields."Service type"?.value?.value
    if (serviceTypeValue == "Defect") {
        issue.typeName = "Bug"
    } else if (["Enhancement", "Documentation", "Request"].contains(serviceTypeValue)) {
        issue.typeName = "Request"
    } else {
        throw new Exception("Invalid Service type value: ${serviceTypeValue}")
    }
}
def setChangeTypeBasedOnServiceType(issue, replica) {
    def serviceTypeValue = replica.customFields."Service type"?.value?.value
    if (issue.issueType?.name != "Bug") {
        if (serviceTypeValue == "Enhancement") {
            issue.customFields."Change Type".value = "Enhancement"
        } else if (["Request", "Documentation"]?.contains(serviceTypeValue)) {
            issue.customFields."Change Type".value = "Consultation"
        } else {
            issue.customFields."Change Type".value = null
        }
    }
}
def setAssigneeFromComponentLead(issue, replica) {
    def leadUser = null
    if (replica.components && replica.components.size() > 0) {
        def componentName = replica.components[0]?.name
        leadUser = getComponentLeadUser(componentName, issue.projectKey)
    }
    issue.assignee = leadUser ?: getDefaultUser()
}
def syncBasicFields(issue, replica) {
    issue.summary = replica.summary
    issue.attachments = attachmentHelper.mergeAttachments(issue, replica)
}
def syncComments(issue, replica) {
    issue.comments = commentHelper.mergeComments(issue, replica, { it.internal = false; it })
}
def syncVersions(issue, replica) {
    def project = nodeHelper.getProject(issue.projectKey)
    // Handle Fix Versions
    issue.fixVersions = replica.fixVersions.collect { v ->
        def existingVersion = nodeHelper.getVersion(v.name, project)
        return existingVersion ?: nodeHelper.createVersion(issue, v.name, v.description)
    }
    // Handle Affected Versions
    issue.affectedVersions = replica.affectedVersions.collect { v ->
        def existingVersion = nodeHelper.getVersion(v.name, project)
        return existingVersion ?: nodeHelper.createVersion(issue, v.name, v.description)
    }
}
def syncComponents(issue, replica) {
    def targetProject = issue.project ?: nodeHelper.getProject(issue.projectKey)
    issue.components = replica.components.collect { remoteComponent ->
        def existingComponent = nodeHelper.getComponent(remoteComponent.name, targetProject)
        if (existingComponent) {
            return existingComponent
        }
        return nodeHelper.createComponent(
            issue,
            remoteComponent.name,
            remoteComponent.description,
            null,
            "UNASSIGNED"
        )
    }.findAll { it != null }
}
def syncPriorityToUrgency(issue, replica) {
    def urgencyMapping = [
        "P1-Stopper" : "Critical",
        "P2-High"    : "High",
        "P3-Medium"  : "Medium",
        "P4-Low"     : "Low"
    ]
    def priorityName = replica.priority?.name
    def urgencyValue = urgencyMapping[priorityName] ?: "Medium"
    def urgencyField = issue.customFields."Urgency"
    if (urgencyField) {
        urgencyField.value = urgencyValue
    }
}
def syncPriorityToImpact(issue, replica) {
    def impactMapping = [
        "P1-Stopper" : "Extensive / Widespread",
        "P2-High"    : "Significant / Large",
        "P3-Medium"  : "Moderate / Limited",
        "P4-Low"     : "Minor / Localized"
    ]
    def priorityName = replica.priority?.name
    def impactValue = impactMapping[priorityName] ?: "Moderate / Limited"
    def impactField = issue.customFields."Impact"
    if (impactField) {
        impactField.value = impactValue
    }
}
def syncL1CreateDate(issue, replica) {
    if (replica.created) {
        LocalDate localDate = replica.created.toLocalDateTime().toLocalDate()
        LocalDateTime startOfDay = localDate.atStartOfDay()
        Timestamp truncatedTimestamp = Timestamp.valueOf(startOfDay)
        issue.customFields."L1 Create Date".value = truncatedTimestamp
    }
}
def syncL1Status(issue, replica) {
    issue.customFields."L1 Status".value = replica.status.name
}
def syncReporter(issue, replica) {
    def localUser = replica.assignee?.email ? nodeHelper.getUserByEmail(replica.assignee.email) : null
    issue.reporter = localUser ?: getDefaultUser()
}
 
def syncSource(issue, replica) {
    def sourceValue
    if (replica.project?.key == "ROOMED" || replica.project?.name == "ROOMED") {
        sourceValue = "ROOMED"
    } else {
        sourceValue = replica.project?.name ?: replica.project?.key
    }
   if (sourceValue) {
        def option = nodeHelper.getOption(issue, "Source", sourceValue)
        if (option) {
            issue.customFields."Source".value = option
            log.debug("Set 'Source' field to '${sourceValue}'")
        } else {
            log.warn("Could not find option '${sourceValue}' for 'Source'")
        }
    }
}
 def syncRequestedResolutionDate(issue, replica) {
    issue.customFields."Requested Resolution Date".value = replica.customFields."Requested Resolution Date"?.value
}
// =====================
// MAIN SYNC EXECUTION FUNCTION
// =====================
def executeSync(projectKey) {
    // First sync specific logic
    if (firstSync) {
        issue.projectKey = projectKey
        issue.description = replica.description
        setIssueTypeBasedOnServiceType(issue, replica)
        setAssigneeFromComponentLead(issue, replica)
    }
    // Set Change Type based on Service Type (only for non-Bug issues)
    setChangeTypeBasedOnServiceType(issue, replica)
    // Execute common synchronization
    syncBasicFields(issue, replica)
    syncComments(issue, replica)
    syncVersions(issue, replica)
    syncComponents(issue, replica)
    syncPriorityToUrgency(issue, replica)
    syncPriorityToImpact(issue, replica)
    syncL1CreateDate(issue, replica)
    syncL1Status(issue, replica)
    syncReporter(issue, replica)
    syncSource(issue, replica)
    syncRequestedResolutionDate(issue, replica)
}

getting this error.

com.exalate.api.exception.IssueTrackerException: Exalate has problems while trying to create an issue in this Jira. Details: [InvalidInputException: [Error map: [{customfield_11804=This field is required}]] [Error list: [[]]] at com.exalate.node.compatibility.CompatibilityService.createIssueObject(CompatibilityService.java:652) at com.exalate.node.hubobject.v1_4.NodeHubIssueHelper.createIssue(NodeHubIssueHelper.java:630) at com.exalate.node.hubobject.v1_4.NodeHubIssueHelper.createIssueWith(NodeHubIssueHelper.java:527) at com.exalate.compatibility.HubObjectHelperAdapter.createNodeIssueWith(HubObjectHelperAdapter.java:37) at com.exalate.hubobject.v1_2.HubObjectHelper.createNodeIssueWith(HubObjectHelper.java:327) at com.exalate.processor.jira.JiraCreateIssueProcessor.applyProcessorResult(JiraCreateIssueProcessor.java:412) at com.exalate.processor.jira.JiraCreateIssueProcessor.applyProcessorResult(JiraCreateIssueProcessor.java:388) at com.exalate.processor.jira.JiraCreateIssueProcessor.createIssue(JiraCreateIssueProcessor.java:204) at com.exalate.replication.request.CreateIssueSyncRequestState.transition(CreateIssueSyncRequestState.java:81) at com.exalate.replication.request.CreateIssueSyncRequestState.transition(CreateIssueSyncRequestState.java:28) at com.exalate.replication.in.RequestProcessorService.processSyncRequest(RequestProcessorService.java:400) at com.exalate.replication.in.RequestProcessorService.processSyncRequestsForIssue(RequestProcessorService.java:273) at com.exalate.replication.in.RequestProcessorService.processRequestsByTwinTraceInternal(RequestProcessorService.java:217) at com.exalate.replication.in.RequestProcessorService.processRequestsByTwinTrace(RequestProcessorService.java:181) at com.exalate.replication.SyncWorkerService.processInQueueForAssignment(SyncWorkerService.java:149) at com.exalate.replication.SyncWorkerService.processInQueueIfAssigned(SyncWorkerService.java:86) at com.exalate.replication.SyncWorkerService.doSyncWork(SyncWorkerService.java:50) at com.exalate.replication.akka.SyncWorkerActor.processAllAssignmentsForWorker(SyncWorkerActor.java:150) at com.exalate.replication.akka.SyncWorkerActor.onReceive(SyncWorkerActor.java:106) at akka.actor.UntypedAbstractActor$$anonfun$receive$1.applyOrElse(AbstractActor.scala:332) at akka.actor.Actor.aroundReceive(Actor.scala:537) at akka.actor.Actor.aroundReceive$(Actor.scala:535) at akka.actor.AbstractActor.aroundReceive(AbstractActor.scala:220) at akka.actor.ActorCell.receiveMessage(ActorCell.scala:579) at akka.actor.ActorCell.invoke(ActorCell.scala:547) at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270) at akka.dispatch.Mailbox.run(Mailbox.scala:231) at akka.dispatch.Mailbox.exec(Mailbox.scala:243) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) Caused by: com.atlassian.jira.exception.CreateException: Error occurred while creating issue through workflow: at com.atlassian.jira.issue.managers.DefaultIssueManager.createIssue(DefaultIssueManager.java:503) at com.atlassian.jira.issue.managers.DefaultIssueManager.createIssueObject(DefaultIssueManager.java:600) at com.atlassian.jira.issue.managers.RequestCachingIssueManager.createIssueObject(RequestCachingIssueManager.java:212) at jdk.internal.reflect.GeneratedMethodAccessor10095.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at com.atlassian.plugin.util.ContextClassLoaderSettingInvocationHandler.invoke(ContextClassLoaderSettingInvocationHandler.java:26) at jdk.proxy3/jdk.proxy3.$Proxy470.createIssueObject(Unknown Source) at jdk.internal.reflect.GeneratedMethodAccessor10095.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at com.atlassian.plugin.osgi.bridge.external.HostComponentFactoryBean$DynamicServiceInvocationHandler.invoke(HostComponentFactoryBean.java:134) at jdk.proxy3/jdk.proxy3.$Proxy470.createIssueObject(Unknown Source) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at com.exalate.util.reflection.ReflectionUtils.invokeMethod(ReflectionUtils.java:102) at com.exalate.node.compatibility.CompatibilityService.createIssueObject(CompatibilityService.java:637) … 32 more Caused by: [InvalidInputException: [Error map: [{customfield_11804=This field is required}]] [Error list: [[]]] at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481) at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:73) at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:108) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:268) at com.onresolve.scriptrunner.canned.jira.utils.ScopeAwareExceptionMapper.convertException(ScopeAwareExceptionMapper.groovy:26) at com.onresolve.scriptrunner.canned.jira.utils.ScopeAwareExceptionMapper$convertException.call(Unknown Source) at com.onresolve.jira.groovy.GroovyValidator.validate(GroovyValidator.groovy:59) at com.atlassian.jira.workflow.SkippableValidator.validate(SkippableValidator.java:45) at com.opensymphony.workflow.AbstractWorkflow.verifyInputs(AbstractWorkflow.java:1466) at com.opensymphony.workflow.AbstractWorkflow.transitionWorkflow(AbstractWorkflow.java:1167) at com.opensymphony.workflow.AbstractWorkflow.initialize(AbstractWorkflow.java:606) at com.atlassian.jira.workflow.OSWorkflowManager.createIssue(OSWorkflowManager.java:753) at com.atlassian.jira.issue.managers.DefaultIssueManager.createIssue(DefaultIssueManager.java:497) … 50 more

Hi Ashar,

Any update on this.

Thanks,
Prasad Ramakrishnan

Hi @pramak1X ,

Thanks for sharing the full script and the error log. I’ve checked it and found the possible cause.

What was happening?

Your syncSource function picks the value to show in the “Source” field like this:

sourceValue = replica.project?.name ?: replica.project?.key

For a project that is not masked, this uses the project name. But the “Source” field is a Select List, and its options are set up using project keys (like AMRI, ROOMED). So the lookup nodeHelper.getOption(issue, "Source", sourceValue) does not find a match for real project names, the field stays empty, and since “Source” is a required field, Jira blocks the issue from being created. That is the customfield_11804 is required error you saw.

Here is a recommended Fix:

Match on project key everywhere, not name. Masking already works correctly in your Outgoing script (it rewrites .key to ROOMED for listed projects), so on Incoming side we only need to read replica.project?.key — no need to check name at all.

def syncSource(issue, replica) {
    def sourceKey = replica.project?.key
    if (sourceKey) {
        def option = nodeHelper.getOption(issue, "Source", sourceKey)
        if (option) {
            issue.customFields."Source".value = option
            log.debug("Set 'Source' field using key '${sourceKey}'")
        } else {
            log.error("No 'Source' option found for key '${sourceKey}' — issue creation will fail since this field is required. Add '${sourceKey}' as an option in the 'Source' Select List.")
        }
    }
}

Since “Source” is a Select List field, it only accepts values that already exist as options. So every real source project key that can ever sync into this connection must already have a matching option in “Source” (in addition to ROOMED for masked ones).

If any source project key does not have a matching option yet, that specific project will still fail to create an issue, same as it did with AMRI before, just for a different reason (missing option vs. wrong lookup value). The updated script logs exactly which key is missing when this happens, so it is easy to spot and add.

You can test it with following steps:

  1. Confirm the “Source” Select List already has an option for every real source project key you expect to sync (not just AMRI and ROOMED).
  2. Update only the syncSource function in your Incoming script to the corrected version (full script shared separately).
  3. Test with two cases:
    • A masked project (e.g. AMRI) → “Source” should show ROOMED.
    • A non-masked project → “Source” should show its real project key (confirm its key already exists as an option first).

Note: No changes needed on the Outgoing script — that part is already correct.

Let me know how the test goes. Thank you!

Kind regards,

Ashar

This is not correct. Source field options are actual Project names and not Project keys.

will this script works.

The issue is fixed. The error was caused by a case mismatch in the script: the Source field value is “Roomed”, while the script was using “ROOMED”.

Hi @pramak1X ,

I’m glad to know that this helped resolve the issue. I would appreciate it if you could mark the reply that helped you resolve the issue as SOLVED.

Kind regards,

Ashar