From 667104fbe3e16ef551872b57b4d2581e4cbce812 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:54:00 +0000 Subject: [PATCH 1/2] Initial plan From 54488ef0bd9f96feae2fb5ab1e584f3ce37b1c33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:01:21 +0000 Subject: [PATCH 2/2] Fix: recurse into nested []any in expandConfigStrings Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --- engine.go | 26 ++++++++++++++++++-------- engine_secrets_test.go | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/engine.go b/engine.go index beb206fe..ef74a20b 100644 --- a/engine.go +++ b/engine.go @@ -992,15 +992,25 @@ func expandConfigStrings(resolver *secrets.MultiResolver, cfg map[string]any) { case map[string]any: expandConfigStrings(resolver, val) case []any: - for i, item := range val { - if s, ok := item.(string); ok { - if expanded, err := resolver.Expand(ctx, s); err == nil { - val[i] = expanded - } - } else if m, ok := item.(map[string]any); ok { - expandConfigStrings(resolver, m) - } + expandConfigSlice(resolver, val) + } + } +} + +// expandConfigSlice expands ${...} placeholders in a slice, recursing into nested +// maps and slices to support arbitrary nesting depth. +func expandConfigSlice(resolver *secrets.MultiResolver, items []any) { + ctx := context.Background() + for i, item := range items { + switch v := item.(type) { + case string: + if expanded, err := resolver.Expand(ctx, v); err == nil { + items[i] = expanded } + case map[string]any: + expandConfigStrings(resolver, v) + case []any: + expandConfigSlice(resolver, v) } } } diff --git a/engine_secrets_test.go b/engine_secrets_test.go index 4f4b8d12..15a89a9e 100644 --- a/engine_secrets_test.go +++ b/engine_secrets_test.go @@ -195,6 +195,28 @@ func TestExpandConfigStrings_DeeplyNestedArrayOfMaps(t *testing.T) { } } +func TestExpandConfigStrings_NestedArrayOfArrays(t *testing.T) { + t.Setenv("CLIENT_ID", "test1") + + resolver := secrets.NewMultiResolver() + cfg := map[string]any{ + "roleAssignments": []any{ + []any{"${CLIENT_ID}", "api_client"}, + }, + } + + expandConfigStrings(resolver, cfg) + + assignments := cfg["roleAssignments"].([]any) + inner := assignments[0].([]any) + if inner[0] != "test1" { + t.Errorf("expected 'test1', got %v", inner[0]) + } + if inner[1] != "api_client" { + t.Errorf("expected 'api_client', got %v", inner[1]) + } +} + func TestExpandConfigStrings_UnresolvablePreserved(t *testing.T) { resolver := secrets.NewMultiResolver() cfg := map[string]any{