@@ -301,10 +301,20 @@ private StartOperationResponse handleStartOperation(
301301 "Invalid link URL: " + link .getUrl (),
302302 e );
303303 }
304+ // LinkConverter only returns a WorkflowEvent-shaped common.v1.Link; nexus links of
305+ // other shapes (e.g. non-temporal URLs) come back null and are intentionally not
306+ // forwarded onto SignalWorkflowExecutionRequest.links, which requires the
307+ // WorkflowEvent variant. Log so a debugging session can see what was dropped.
304308 io .temporal .api .common .v1 .Link commonLink =
305309 LinkConverter .nexusLinkToWorkflowEvent (link );
306310 if (commonLink != null ) {
307311 inboundCommonLinks .add (commonLink );
312+ } else {
313+ log .warn (
314+ "Dropping inbound Nexus link from outbound signal propagation: type='{}',"
315+ + " url='{}' (not a parseable temporal WorkflowEvent link)" ,
316+ link .getType (),
317+ link .getUrl ());
308318 }
309319 });
310320 CurrentNexusOperationContext .get ().setNexusOperationLinks (inboundCommonLinks );
@@ -318,27 +328,30 @@ private StartOperationResponse handleStartOperation(
318328 try {
319329 OperationStartResult <HandlerResultContent > result =
320330 startOperation (context , operationStartDetails .build (), input .build ());
321- // If a signal RPC issued by the handler returned a backlink, propagate it to the caller
322- // so the caller workflow's history event links to the signal event on the callee. Same
323- // backlink applies to both sync and async response variants.
324- io .temporal .api .nexus .v1 .Link signalBacklink = null ;
325- io .temporal .api .common .v1 .Link signalResponseLink =
326- CurrentNexusOperationContext .get ().getSignalWorkflowResponseLink ();
327- if (signalResponseLink != null && signalResponseLink .hasWorkflowEvent ()) {
328- signalBacklink =
331+ // If signal/signalWithStart RPCs issued by the handler returned backlinks, propagate
332+ // them to the caller so the caller workflow's history event links to each signal event
333+ // on the callee. Same set of backlinks applies to both sync and async response variants.
334+ List <io .temporal .api .nexus .v1 .Link > signalBacklinks = new ArrayList <>();
335+ for (io .temporal .api .common .v1 .Link signalResponseLink :
336+ CurrentNexusOperationContext .get ().getSignalWorkflowResponseLinks ()) {
337+ if (!signalResponseLink .hasWorkflowEvent ()) {
338+ continue ;
339+ }
340+ io .temporal .api .nexus .v1 .Link converted =
329341 LinkConverter .workflowEventToNexusLink (signalResponseLink .getWorkflowEvent ());
342+ if (converted != null ) {
343+ signalBacklinks .add (converted );
344+ }
330345 }
331346
332347 if (result .isSync ()) {
333- StartOperationResponse . Sync . Builder syncBuilder =
348+ startResponseBuilder . setSyncSuccess (
334349 StartOperationResponse .Sync .newBuilder ()
335- .setPayload (Payload .parseFrom (result .getSyncResult ().getDataBytes ()));
336- if (signalBacklink != null ) {
337- syncBuilder .addLinks (signalBacklink );
338- }
339- startResponseBuilder .setSyncSuccess (syncBuilder .build ());
350+ .setPayload (Payload .parseFrom (result .getSyncResult ().getDataBytes ()))
351+ .addAllLinks (signalBacklinks )
352+ .build ());
340353 } else {
341- StartOperationResponse . Async . Builder asyncBuilder =
354+ startResponseBuilder . setAsyncSuccess (
342355 StartOperationResponse .Async .newBuilder ()
343356 .setOperationId (result .getAsyncOperationToken ())
344357 .setOperationToken (result .getAsyncOperationToken ())
@@ -350,11 +363,9 @@ private StartOperationResponse handleStartOperation(
350363 .setType (link .getType ())
351364 .setUrl (link .getUri ().toString ())
352365 .build ())
353- .collect (Collectors .toList ()));
354- if (signalBacklink != null ) {
355- asyncBuilder .addLinks (signalBacklink );
356- }
357- startResponseBuilder .setAsyncSuccess (asyncBuilder .build ());
366+ .collect (Collectors .toList ()))
367+ .addAllLinks (signalBacklinks )
368+ .build ());
358369 }
359370 } catch (OperationException e ) {
360371 throw e ;
0 commit comments