STET

agent_patch

runs/2026-02-21__03-36-16__gpt-5-1-codex-mini/flux-pr-817/flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/agent-logs/agent.patch

23496 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/datasource/pubsub_datasource/nats_event_manager.go b/app/v2/pkg/engine/datasource/pubsub_datasource/nats_event_manager.go
index 1cb2914..26b5b45 100644
--- a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/datasource/pubsub_datasource/nats_event_manager.go
+++ b/app/v2/pkg/engine/datasource/pubsub_datasource/nats_event_manager.go
@@ -3,7 +3,8 @@ package pubsub_datasource
 import (
 	"encoding/json"
 	"fmt"
-	"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
+	"strings"
+
 	"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/plan"
 	"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
 	"slices"
@@ -35,37 +36,37 @@ type NatsEventManager struct {
 }
 
 func (p *NatsEventManager) extractEventSubject(ref int, subject string) (string, error) {
-	matches := eventSubjectRegex.FindAllStringSubmatch(subject, -1)
-	if len(matches) != 1 || len(matches[0]) != 2 {
-		return "", fmt.Errorf("expected subject to match regex")
-	}
-	argumentName := matches[0][1]
-	// We need to find the argument in the operation
-	argumentRef, ok := p.visitor.Operation.FieldArgument(ref, []byte(argumentName))
-	if !ok {
-		return "", fmt.Errorf("argument \"%s\" is not defined", argumentName)
-	}
-	argumentValue := p.visitor.Operation.ArgumentValue(argumentRef)
-	if argumentValue.Kind != ast.ValueKindVariable {
-		return "", fmt.Errorf("expected argument \"%s\" kind to be \"ValueKindVariable\" but received \"%s\"", argumentName, argumentValue.Kind)
-	}
-	variableName := p.visitor.Operation.VariableValueNameBytes(argumentValue.Ref)
-	variableDefinition, ok := p.visitor.Operation.VariableDefinitionByNameAndOperation(p.visitor.Walker.Ancestors[0].Ref, variableName)
-	if !ok {
-		return "", fmt.Errorf("expected definition to exist for variable \"%s\"", variableName)
+	parser := plan.NewArgumentTemplateParser(p.visitor.Operation, p.visitor.Definition, p.visitor.Walker, ref)
+	rendererFactory := func(variableTypeRef int, variablePath []string) (resolve.VariableRenderer, error) {
+		return resolve.NewPlainVariableRendererWithValidationFromTypeRef(p.visitor.Operation, p.visitor.Definition, variableTypeRef, variablePath...)
 	}
-	variableTypeRef := p.visitor.Operation.VariableDefinitions[variableDefinition].Type
-	renderer, err := resolve.NewPlainVariableRendererWithValidationFromTypeRef(p.visitor.Operation, p.visitor.Definition, variableTypeRef, string(variableName))
+	segments, err := parser.Parse(subject, rendererFactory)
 	if err != nil {
-		return "", err
+		return "", fmt.Errorf("failed to parse event subject template %q: %w", subject, err)
+	}
+
+	var builder strings.Builder
+	var seenVariable bool
+	for _, segment := range segments {
+		switch segment.SegmentType {
+		case resolve.StaticSegmentType:
+			builder.Write(segment.Data)
+		case resolve.VariableSegmentType:
+			seenVariable = true
+			contextVariable := &resolve.ContextVariable{
+				Path:     segment.VariableSourcePath,
+				Renderer: segment.Renderer,
+			}
+			placeholder, _ := p.variables.AddVariable(contextVariable)
+			builder.WriteString(placeholder)
+		default:
+			return "", fmt.Errorf("unexpected segment type %d in subject template %q", segment.SegmentType, subject)
+		}
 	}
-	contextVariable := &resolve.ContextVariable{
-		Path:     []string{string(variableName)},
-		Renderer: renderer,
+	if !seenVariable {
+		return "", fmt.Errorf("subject %q must contain at least one argument template", subject)
 	}
-	// We need to replace the template literal with the variable placeholder (and reuse if it already exists)
-	variablePlaceHolder, _ := p.variables.AddVariable(contextVariable) // $$0$$
-	return eventSubjectRegex.ReplaceAllLiteralString(subject, variablePlaceHolder), nil
+	return builder.String(), nil
 }
 
 func (p *NatsEventManager) eventDataBytes(ref int) ([]byte, error) {
diff --git a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/datasource/pubsub_datasource/pubsub_datasource.go b/app/v2/pkg/engine/datasource/pubsub_datasource/pubsub_datasource.go
index 02b05d3..4c62615 100644
--- a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/datasource/pubsub_datasource/pubsub_datasource.go
+++ b/app/v2/pkg/engine/datasource/pubsub_datasource/pubsub_datasource.go
@@ -5,7 +5,6 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"regexp"
 	"strings"
 
 	"github.com/jensneuse/abstractlogger"
@@ -23,8 +22,6 @@ const (
 	EventTypeSubscribe EventType = "subscribe"
 )
 
-var eventSubjectRegex = regexp.MustCompile(`{{ args.([a-zA-Z0-9_]+) }}`)
-
 func EventTypeFromString(s string) (EventType, error) {
 	et := EventType(strings.ToLower(s))
 	switch et {
diff --git a/app/v2/pkg/engine/plan/argument_template_parser.go b/app/v2/pkg/engine/plan/argument_template_parser.go
new file mode 100644
index 0000000..39dfd92
--- /dev/null
+++ b/app/v2/pkg/engine/plan/argument_template_parser.go
@@ -0,0 +1,219 @@
+package plan
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
+)
+
+var argumentTemplateRegex = regexp.MustCompile(`{{\s*args((?:\.[a-zA-Z0-9_]+)+)\s*}}`)
+
+// VariableRendererFactory returns a renderer for a context variable identified by its type and path.
+type VariableRendererFactory func(variableTypeRef int, variablePath []string) (resolve.VariableRenderer, error)
+
+// ArgumentTemplateParser parses GraphQL argument templates that target field arguments.
+type ArgumentTemplateParser struct {
+	operation  *ast.Document
+	definition *ast.Document
+	walker     *astvisitor.Walker
+	fieldRef   int
+}
+
+// NewArgumentTemplateParser builds a parser for the provided field.
+func NewArgumentTemplateParser(operation, definition *ast.Document, walker *astvisitor.Walker, fieldRef int) *ArgumentTemplateParser {
+	return &ArgumentTemplateParser{
+		operation:  operation,
+		definition: definition,
+		walker:     walker,
+		fieldRef:   fieldRef,
+	}
+}
+
+// Parse scans value for argument templates and returns a segment list suitable for InputTemplate usage.
+// rendererFactory constructs a renderer for each detected variable segment. If nil, a plain renderer is used.
+func (p *ArgumentTemplateParser) Parse(value string, rendererFactory VariableRendererFactory) ([]resolve.TemplateSegment, error) {
+	if rendererFactory == nil {
+		rendererFactory = func(variableTypeRef int, variablePath []string) (resolve.VariableRenderer, error) {
+			return resolve.NewPlainVariableRenderer(), nil
+		}
+	}
+
+	payload := []byte(value)
+	matches := argumentTemplateRegex.FindAllSubmatchIndex(payload, -1)
+	if len(matches) == 0 {
+		return []resolve.TemplateSegment{{SegmentType: resolve.StaticSegmentType, Data: append([]byte(nil), payload...)}}, nil
+	}
+
+	segments := make([]resolve.TemplateSegment, 0, len(matches)*2)
+	last := 0
+
+	for _, match := range matches {
+		start, end := match[0], match[1]
+		if start > last {
+			segments = append(segments, resolve.TemplateSegment{SegmentType: resolve.StaticSegmentType, Data: append([]byte(nil), payload[last:start]...)})
+		}
+
+		segment, err := p.buildVariableSegment(payload[start:end], payload[match[2]:match[3]], rendererFactory)
+		if err != nil {
+			return nil, err
+		}
+		segments = append(segments, segment)
+		last = end
+	}
+
+	if last < len(payload) {
+		segments = append(segments, resolve.TemplateSegment{SegmentType: resolve.StaticSegmentType, Data: append([]byte(nil), payload[last:]...)})
+	}
+
+	return segments, nil
+}
+
+func (p *ArgumentTemplateParser) buildVariableSegment(literal []byte, pathBytes []byte, rendererFactory VariableRendererFactory) (resolve.TemplateSegment, error) {
+	argumentPath := strings.Split(string(pathBytes[1:]), ".")
+	if len(argumentPath) == 0 {
+		return resolve.TemplateSegment{}, fmt.Errorf("argument template %q is incomplete", literal)
+	}
+
+	argumentName := argumentPath[0]
+	argumentRef, ok := p.operation.FieldArgument(p.fieldRef, []byte(argumentName))
+	if !ok {
+		return resolve.TemplateSegment{}, fmt.Errorf("argument template %q references undefined argument %q", literal, argumentName)
+	}
+
+	argumentValue := p.operation.ArgumentValue(argumentRef)
+	if argumentValue.Kind != ast.ValueKindVariable {
+		return resolve.TemplateSegment{}, fmt.Errorf("argument template %q expects argument %q to be a variable", literal, argumentName)
+	}
+
+	variableName := p.operation.VariableValueNameString(argumentValue.Ref)
+	variableDefRef, ok := p.operation.VariableDefinitionByNameAndOperation(p.walker.Ancestors[0].Ref, []byte(variableName))
+	if !ok {
+		return resolve.TemplateSegment{}, fmt.Errorf("argument template %q references unknown variable %q", literal, variableName)
+	}
+
+	variableTypeRef := p.operation.VariableDefinitions[variableDefRef].Type
+	nestedSegments := argumentPath[1:]
+	fullPath := "args." + strings.Join(argumentPath, ".")
+
+	if err := p.validateArgumentPath(variableTypeRef, nestedSegments, fullPath); err != nil {
+		return resolve.TemplateSegment{}, err
+	}
+
+	variablePath := make([]string, 0, 1+len(nestedSegments))
+	variablePath = append(variablePath, variableName)
+	variablePath = append(variablePath, nestedSegments...)
+
+	renderer, err := rendererFactory(variableTypeRef, variablePath)
+	if err != nil {
+		return resolve.TemplateSegment{}, fmt.Errorf("argument template %q: %w", literal, err)
+	}
+	if renderer == nil {
+		renderer = resolve.NewPlainVariableRenderer()
+	}
+
+	return resolve.TemplateSegment{
+		SegmentType:        resolve.VariableSegmentType,
+		VariableKind:       resolve.ContextVariableKind,
+		VariableSourcePath: append([]string(nil), variablePath...),
+		Renderer:           renderer,
+	}, nil
+}
+
+func (p *ArgumentTemplateParser) validateArgumentPath(variableTypeRef int, nestedSegments []string, fullPath string) error {
+	if len(nestedSegments) == 0 {
+		if !isLeafType(p.operation, p.definition, variableTypeRef) {
+			return fmt.Errorf("argument template %s must resolve to a leaf type", fullPath)
+		}
+		return nil
+	}
+
+	currentNode, err := p.inputObjectNodeFromOperationType(variableTypeRef, fullPath)
+	if err != nil {
+		return fmt.Errorf("argument template %s: %w", fullPath, err)
+	}
+
+	for idx, segment := range nestedSegments {
+		fieldRef, ok := p.definition.NodeInputFieldDefinitionByName(currentNode, []byte(segment))
+		if !ok {
+			return fmt.Errorf("argument template %s references unknown field %q", fullPath, segment)
+		}
+		fieldTypeRef := p.definition.InputValueDefinitions[fieldRef].Type
+		if idx == len(nestedSegments)-1 {
+			if !isLeafType(p.definition, p.definition, fieldTypeRef) {
+				return fmt.Errorf("argument template %s must resolve to a leaf type", fullPath)
+			}
+			break
+		}
+		nextNode, err := p.inputObjectNodeFromDefinitionType(fieldTypeRef)
+		if err != nil {
+			return fmt.Errorf("argument template %s: %w", fullPath, err)
+		}
+		currentNode = nextNode
+	}
+
+	return nil
+}
+
+func (p *ArgumentTemplateParser) inputObjectNodeFromOperationType(typeRef int, fullPath string) (ast.Node, error) {
+	baseRef := unwrapNonNull(p.operation, typeRef)
+	if baseRef >= len(p.operation.Types) {
+		return ast.Node{}, fmt.Errorf("argument template %s references invalid type", fullPath)
+	}
+	if p.operation.Types[baseRef].TypeKind == ast.TypeKindList {
+		return ast.Node{}, fmt.Errorf("argument template %s references a list type", fullPath)
+	}
+	if p.operation.Types[baseRef].TypeKind != ast.TypeKindNamed {
+		return ast.Node{}, fmt.Errorf("argument template %s references a non-named type", fullPath)
+	}
+	typeName := p.operation.TypeNameString(baseRef)
+	node, ok := p.definition.Index.FirstNodeByNameStr(typeName)
+	if !ok || node.Kind != ast.NodeKindInputObjectTypeDefinition {
+		return ast.Node{}, fmt.Errorf("argument template %s references non-input object type %s", fullPath, typeName)
+	}
+	return node, nil
+}
+
+func (p *ArgumentTemplateParser) inputObjectNodeFromDefinitionType(typeRef int) (ast.Node, error) {
+	baseRef := unwrapNonNull(p.definition, typeRef)
+	if baseRef >= len(p.definition.Types) {
+		return ast.Node{}, fmt.Errorf("type reference %d is invalid", typeRef)
+	}
+	if p.definition.Types[baseRef].TypeKind == ast.TypeKindList {
+		return ast.Node{}, fmt.Errorf("type %s is a list", p.definition.TypeNameString(baseRef))
+	}
+	if p.definition.Types[baseRef].TypeKind != ast.TypeKindNamed {
+		return ast.Node{}, fmt.Errorf("type reference is not a named type")
+	}
+	typeName := p.definition.TypeNameString(baseRef)
+	node, ok := p.definition.Index.FirstNodeByNameStr(typeName)
+	if !ok || node.Kind != ast.NodeKindInputObjectTypeDefinition {
+		return ast.Node{}, fmt.Errorf("type %s is not an input object", typeName)
+	}
+	return node, nil
+}
+
+func unwrapNonNull(doc *ast.Document, typeRef int) int {
+	for typeRef < len(doc.Types) && doc.Types[typeRef].TypeKind == ast.TypeKindNonNull {
+		typeRef = doc.Types[typeRef].OfType
+	}
+	return typeRef
+}
+
+func isLeafType(doc, definition *ast.Document, typeRef int) bool {
+	if typeRef >= len(doc.Types) {
+		return false
+	}
+	switch doc.Types[typeRef].TypeKind {
+	case ast.TypeKindNonNull:
+		return isLeafType(doc, definition, doc.Types[typeRef].OfType)
+	case ast.TypeKindList:
+		return isLeafType(doc, definition, doc.Types[typeRef].OfType)
+	case ast.TypeKindNamed:
+		return doc.TypeIsScalar(typeRef, definition) || doc.TypeIsEnum(typeRef, definition)
+	}
+	return false
+}
diff --git a/app/v2/pkg/engine/plan/argument_template_parser_test.go b/app/v2/pkg/engine/plan/argument_template_parser_test.go
new file mode 100644
index 0000000..c197ba3
--- /dev/null
+++ b/app/v2/pkg/engine/plan/argument_template_parser_test.go
@@ -0,0 +1,95 @@
+package plan
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/astnormalization"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/asttransform"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/astvalidation"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/internal/unsafeparser"
+	"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
+)
+
+func setupArgumentTemplateParser(t *testing.T) *ArgumentTemplateParser {
+	t.Helper()
+
+	const schema = `schema { query: Query }
+    type Query {
+        hero(filter: Input!, second: String!): String
+    }
+
+    input Input {
+        nested: NestedInput!
+    }
+
+    input NestedInput {
+        name: String!
+    }
+    `
+	const operation = `query Template($a: Input!, $b: String!) {
+        hero(filter: $a, second: $b)
+    }`
+
+	def := unsafeparser.ParseGraphqlDocumentString(schema)
+	op := unsafeparser.ParseGraphqlDocumentString(operation)
+	require.NoError(t, asttransform.MergeDefinitionWithBaseSchema(&def))
+
+	var report operationreport.Report
+	normalizer := astnormalization.NewNormalizer(true, true)
+	normalizer.NormalizeOperation(&op, &def, &report)
+	validator := astvalidation.DefaultOperationValidator()
+	validator.Validate(&op, &def, &report)
+	require.False(t, report.HasErrors(), "operation did not validate: %v", report.Error())
+
+	walker := &astvisitor.Walker{
+		Ancestors: []ast.Node{
+			{
+				Kind: ast.NodeKindOperationDefinition,
+				Ref:  0,
+			},
+		},
+	}
+
+	selectionSet := op.OperationDefinitions[0].SelectionSet
+	fieldSelections := op.SelectionSetFieldSelections(selectionSet)
+	require.Len(t, fieldSelections, 1)
+
+	return NewArgumentTemplateParser(&op, &def, walker, fieldSelections[0])
+}
+
+func TestArgumentTemplateParser_MultipleTemplates(t *testing.T) {
+	parser := setupArgumentTemplateParser(t)
+
+	segments, err := parser.Parse("topic.{{ args.filter.nested.name }}.{{ args.second }}.suffix", nil)
+	require.NoError(t, err)
+	require.Len(t, segments, 5)
+
+	require.Equal(t, []byte("topic."), segments[0].Data)
+	require.Equal(t, resolve.VariableSegmentType, segments[1].SegmentType)
+	require.Equal(t, []string{"a", "nested", "name"}, segments[1].VariableSourcePath)
+	require.NotNil(t, segments[1].Renderer)
+
+	require.Equal(t, []byte("."), segments[2].Data)
+	require.Equal(t, resolve.VariableSegmentType, segments[3].SegmentType)
+	require.Equal(t, []string{"b"}, segments[3].VariableSourcePath)
+	require.NotNil(t, segments[3].Renderer)
+
+	require.Equal(t, []byte(".suffix"), segments[4].Data)
+}
+
+func TestArgumentTemplateParser_InvalidPathFails(t *testing.T) {
+	parser := setupArgumentTemplateParser(t)
+
+	_, err := parser.Parse("{{ args.filter }}", nil)
+	require.Error(t, err)
+	require.Contains(t, err.Error(), "must resolve to a leaf type")
+
+	_, err = parser.Parse("{{ args.filter.missing }}", nil)
+	require.Error(t, err)
+	require.Contains(t, err.Error(), "unknown field")
+}
diff --git a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/plan/configuration_visitor.go b/app/v2/pkg/engine/plan/configuration_visitor.go
index b5e2ed1..512e89e 100644
--- a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/plan/configuration_visitor.go
+++ b/app/v2/pkg/engine/plan/configuration_visitor.go
@@ -1,9 +1,7 @@
 package plan
 
 import (
-	"bytes"
 	"fmt"
-	"regexp"
 	"slices"
 	"strings"
 
@@ -870,85 +868,19 @@ func (c *configurationVisitor) buildSubscriptionFilterCondition(condition Subscr
 	return filter
 }
 
-var (
-	// subscriptionFieldFilterRegex is used to extract the variable name from the subscription filter condition
-	// e.g. {{ args.id }} -> id
-	// e.g. {{ args.input.id }} -> input.id
-	subscriptionFieldFilterRegex = regexp.MustCompile(`{{\s*args((?:\.[a-zA-Z0-9_]+)+)\s*}}`)
-)
-
-// ContainsTemplateString checks if the value contains a template string
-func ContainsTemplateString(value []byte) bool {
-	return bytes.Contains(value, []byte("{{"))
-}
-
 func (c *configurationVisitor) buildSubscriptionFieldFilter(condition *SubscriptionFieldCondition) *resolve.SubscriptionFieldFilter {
-	filter := &resolve.SubscriptionFieldFilter{}
-	filter.FieldPath = condition.FieldPath
-	filter.Values = make([]resolve.InputTemplate, len(condition.Values))
+	filter := &resolve.SubscriptionFieldFilter{
+		FieldPath: condition.FieldPath,
+		Values:    make([]resolve.InputTemplate, len(condition.Values)),
+	}
+	parser := NewArgumentTemplateParser(c.operation, c.definition, c.walker, c.fieldRef)
 	for i, value := range condition.Values {
-		matches := subscriptionFieldFilterRegex.FindAllStringSubmatchIndex(value, -1)
-		if len(matches) == 0 {
-			filter.Values[i].Segments = []resolve.TemplateSegment{
-				{
-					SegmentType: resolve.StaticSegmentType,
-					Data:        []byte(value),
-				},
-			}
-			continue
+		segments, err := parser.Parse(value, nil)
+		if err != nil {
+			c.walker.StopWithInternalErr(fmt.Errorf("failed to parse subscription filter template %q: %w", value, err))
+			return nil
 		}
-		if len(matches) == 1 && len(matches[0]) == 4 {
-			prefix := value[:matches[0][0]]
-			hasPrefix := len(prefix) > 0
-			// the path begins with ".", so ignore the first empty string element with trailing [1:]
-			argumentPath := strings.Split(value[matches[0][2]:matches[0][3]][1:], ".")
-			argumentName := argumentPath[0]
-			argumentRef, ok := c.operation.FieldArgument(c.fieldRef, []byte(argumentName))
-			if !ok {
-				c.walker.StopWithInternalErr(fmt.Errorf(`field argument "%s" is not defined`, argumentName))
-				return nil
-			}
-			argumentValue := c.operation.ArgumentValue(argumentRef)
-			if argumentValue.Kind != ast.ValueKindVariable {
-				c.walker.StopWithInternalErr(fmt.Errorf(`expected argument "%s" kind to be "ValueKindVariable" but received "%s"`, argumentName, argumentValue.Kind))
-				return nil
-			}
-			variableName := c.operation.VariableValueNameString(argumentValue.Ref)
-			// the variable path should be the variable name, e.g., "a", and then the 2nd element from the path onwards
-			variablePath := append([]string{variableName}, argumentPath[1:]...)
-			suffix := value[matches[0][1]:]
-			hasSuffix := len(suffix) > 0
-			size := 1
-			if hasPrefix {
-				size++
-			}
-			if hasSuffix {
-				size++
-			}
-			filter.Values[i].Segments = make([]resolve.TemplateSegment, size)
-			idx := 0
-			if hasPrefix {
-				filter.Values[i].Segments[idx] = resolve.TemplateSegment{
-					SegmentType: resolve.StaticSegmentType,
-					Data:        []byte(prefix),
-				}
-				idx++
-			}
-			filter.Values[i].Segments[idx] = resolve.TemplateSegment{
-				SegmentType:        resolve.VariableSegmentType,
-				VariableKind:       resolve.ContextVariableKind,
-				Renderer:           resolve.NewPlainVariableRenderer(),
-				VariableSourcePath: variablePath,
-			}
-			if hasSuffix {
-				filter.Values[i].Segments[idx+1] = resolve.TemplateSegment{
-					SegmentType: resolve.StaticSegmentType,
-					Data:        []byte(suffix),
-				}
-			}
-			continue
-		}
-		return nil
+		filter.Values[i].Segments = segments
 	}
 	return filter
 }
diff --git a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/plan/subscription_filter_test.go b/app/v2/pkg/engine/plan/subscription_filter_test.go
index d6c51d9..1301ef5 100644
--- a/tmp/agent-patch-flux-pr-817.1-of-1.2026-03-05__17-48-26__gpt-5-1-codex-mini/app/v2/pkg/engine/plan/subscription_filter_test.go
+++ b/app/v2/pkg/engine/plan/subscription_filter_test.go
@@ -262,7 +262,7 @@ func TestPlanSubscriptionFilter(t *testing.T) {
 		},
 	))
 
-	t.Run("subscription with IN field invalid filter multiple templates", test(
+	t.Run("subscription with IN field multi template filter", test(
 		schema, `
 				subscription { heroByID(id: "1") { id name } }
 			`, "",
@@ -271,6 +271,33 @@ func TestPlanSubscriptionFilter(t *testing.T) {
 				Trigger: resolve.GraphQLSubscriptionTrigger{
 					Input: []byte{},
 				},
+				Filter: &resolve.SubscriptionFilter{
+					In: &resolve.SubscriptionFieldFilter{
+						FieldPath: []string{"id"},
+						Values: []resolve.InputTemplate{
+							{
+								Segments: []resolve.TemplateSegment{
+									{
+										SegmentType:        resolve.VariableSegmentType,
+										VariableKind:       resolve.ContextVariableKind,
+										VariableSourcePath: []string{"a"},
+										Renderer:           resolve.NewPlainVariableRenderer(),
+									},
+									{
+										SegmentType: resolve.StaticSegmentType,
+										Data:        []byte("."),
+									},
+									{
+										SegmentType:        resolve.VariableSegmentType,
+										VariableKind:       resolve.ContextVariableKind,
+										VariableSourcePath: []string{"a"},
+										Renderer:           resolve.NewPlainVariableRenderer(),
+									},
+								},
+							},
+						},
+					},
+				},
 				Response: &resolve.GraphQLResponse{
 					Data: &resolve.Object{
 						Fields: []*resolve.Field{
@@ -318,7 +345,7 @@ func TestPlanSubscriptionFilter(t *testing.T) {
 					SubscriptionFilterCondition: &SubscriptionFilterCondition{
 						In: &SubscriptionFieldCondition{
 							FieldPath: []string{"id"},
-							Values:    []string{"{{ args.a }}.{{ args.b }}"},
+							Values:    []string{"{{ args.id }}.{{ args.id }}"},
 						},
 					},
 				},