From bef40b7741b0b86c46a8ebccf4b7db4dcdda64e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:14:18 +0000 Subject: [PATCH 1/9] Trim trailing whitespace when emitting custom/on step content Custom steps (agent-job `steps:`) and pre-activation `on.steps` embed user-authored shell/jq/js content verbatim. When those source lines carry trailing whitespace, it survived into the generated `*.lock.yml`, which yamllint flags as trailing-spaces. Right-trim each content line in the three step-emission paths: - addCustomStepsAsIs / addCustomStepsWithRuntimeInsertion (agent job) - ConvertStepToYAML (on.steps in pre-activation job) - writeStepsSection (pre/post/pre-agent steps) Blank/whitespace-only lines were already collapsed to bare newlines; this extends the same treatment to trailing whitespace on content lines. Trailing whitespace is insignificant for the shell/script content these steps carry. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/workflow/compiler_yaml_runtime_setup.go | 15 ++++++++++----- pkg/workflow/compiler_yaml_step_conversion.go | 8 ++++++-- pkg/workflow/compiler_yaml_step_lifecycle.go | 9 ++++++--- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pkg/workflow/compiler_yaml_runtime_setup.go b/pkg/workflow/compiler_yaml_runtime_setup.go index 1331aa4d6cf..49310512552 100644 --- a/pkg/workflow/compiler_yaml_runtime_setup.go +++ b/pkg/workflow/compiler_yaml_runtime_setup.go @@ -319,8 +319,11 @@ func (c *Compiler) addCustomStepsAsIs(yaml *strings.Builder, customSteps string) continue } - // Simply add 6 spaces for job context indentation - yaml.WriteString(" " + line + "\n") + // Simply add 6 spaces for job context indentation. Right-trim so any + // trailing whitespace carried in from source step content (e.g. embedded + // shell/jq/js scripts) doesn't survive into the generated YAML, which + // yamllint flags as trailing-spaces. + yaml.WriteString(" " + strings.TrimRight(line, " \t") + "\n") } } } @@ -348,8 +351,10 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus continue } - // Add the line with proper indentation - yaml.WriteString(" " + line + "\n") + // Add the line with proper indentation. Right-trim so trailing whitespace + // carried in from source step content doesn't survive into the generated + // YAML, which yamllint flags as trailing-spaces. + yaml.WriteString(" " + strings.TrimRight(line, " \t") + "\n") // Check if this line starts a step with "- name:" or "- uses:" trimmed := strings.TrimSpace(line) @@ -392,7 +397,7 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus if nextTrimmed == "" { yaml.WriteString("\n") } else { - yaml.WriteString(" " + nextLine + "\n") + yaml.WriteString(" " + strings.TrimRight(nextLine, " \t") + "\n") } i++ } diff --git a/pkg/workflow/compiler_yaml_step_conversion.go b/pkg/workflow/compiler_yaml_step_conversion.go index ec946c3166b..e3bfc4e59b7 100644 --- a/pkg/workflow/compiler_yaml_step_conversion.go +++ b/pkg/workflow/compiler_yaml_step_conversion.go @@ -42,10 +42,14 @@ func ConvertStepToYAML(stepMap map[string]any) (string, error) { var result strings.Builder for _, line := range lines { - if strings.TrimSpace(line) == "" { + // Right-trim so trailing whitespace carried in from source step content + // (e.g. embedded shell/js/jq inside a literal block scalar) doesn't survive + // into the generated YAML, which yamllint flags as trailing-spaces. Trailing + // whitespace is insignificant for the shell/script content these steps carry. + if trimmed := strings.TrimRight(line, " \t"); strings.TrimSpace(trimmed) == "" { result.WriteString("\n") } else { - result.WriteString(" " + line + "\n") + result.WriteString(" " + trimmed + "\n") } } diff --git a/pkg/workflow/compiler_yaml_step_lifecycle.go b/pkg/workflow/compiler_yaml_step_lifecycle.go index a07ac5ee03e..bff46a0fc21 100644 --- a/pkg/workflow/compiler_yaml_step_lifecycle.go +++ b/pkg/workflow/compiler_yaml_step_lifecycle.go @@ -38,10 +38,13 @@ func writeStepsSection(yaml *strings.Builder, stepsYAML string) { yaml.WriteString("\n") continue } - if strings.HasPrefix(line, " ") { - yaml.WriteString(" " + line[2:] + "\n") + // Emit the right-trimmed line so trailing whitespace carried in from the + // source step content (e.g. embedded shell/jq/js scripts) doesn't survive + // into the generated YAML, which yamllint flags as trailing-spaces. + if strings.HasPrefix(trimmed, " ") { + yaml.WriteString(" " + trimmed[2:] + "\n") } else { - yaml.WriteString(" " + line + "\n") + yaml.WriteString(" " + trimmed + "\n") } } } From 2591dd0a07b03cc6216388e0a31c8b5cdd9a34d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:51:02 +0000 Subject: [PATCH 2/9] plan: revert block-scalar-unsafe line trimming per reviewer feedback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/dataflow-pr-discussion-dataset.lock.yml | 6 +++--- .github/workflows/deep-report.lock.yml | 6 +++--- .github/workflows/issue-monster.lock.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml index b1d3379c44b..833b821afd7 100644 --- a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml +++ b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml @@ -688,12 +688,12 @@ jobs: # Extract discussions and normalize structure echo "$RESULT" | jq -r ' - .data.repository.discussions.nodes + .data.repository.discussions.nodes | map({ - number, + number, title, body, - createdAt, + createdAt, updatedAt, url, category: .category.name, diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 5516b7c9652..1530de732d9 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -709,12 +709,12 @@ jobs: # Extract discussions and normalize structure echo "$RESULT" | jq -r ' - .data.repository.discussions.nodes + .data.repository.discussions.nodes | map({ - number, + number, title, body, - createdAt, + createdAt, updatedAt, url, category: .category.name, diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 58cd66509fb..eab70ad7946 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -2496,8 +2496,8 @@ jobs: } // Exclude issues with open PRs from Copilot coding agent - const openCopilotPRs = issue.linkedPRs?.filter(pr => - pr.state === 'OPEN' && + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) ) || []; if (openCopilotPRs.length > 0) { From e1e7a02fd6294cb2842b75d2ae128e041e602c6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:55:23 +0000 Subject: [PATCH 3/9] revert: remove unsafe block-scalar trimming per reviewer feedback The three emission functions trimmed trailing whitespace from every line including literal/folded block-scalar payloads (run:, script: bodies). This is semantically unsafe: a backslash followed by spaces becomes a bare backslash (line continuation), heredoc indentation can shift, and template literal content can change. The normalizeBlankLines pass already preserves block-scalar content byte-for-byte (documented invariant in compiler_yaml_normalize.go). Trimming at the emission point conflicts with that contract. The 8 trailing-spaces yamllint warnings that prompted this PR all originate from user-authored block-scalar content; they are accepted as a known limitation rather than risking a semantic change. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler_yaml_runtime_setup.go | 15 +++++---------- pkg/workflow/compiler_yaml_step_conversion.go | 8 ++------ pkg/workflow/compiler_yaml_step_lifecycle.go | 9 +++------ 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/pkg/workflow/compiler_yaml_runtime_setup.go b/pkg/workflow/compiler_yaml_runtime_setup.go index 49310512552..1331aa4d6cf 100644 --- a/pkg/workflow/compiler_yaml_runtime_setup.go +++ b/pkg/workflow/compiler_yaml_runtime_setup.go @@ -319,11 +319,8 @@ func (c *Compiler) addCustomStepsAsIs(yaml *strings.Builder, customSteps string) continue } - // Simply add 6 spaces for job context indentation. Right-trim so any - // trailing whitespace carried in from source step content (e.g. embedded - // shell/jq/js scripts) doesn't survive into the generated YAML, which - // yamllint flags as trailing-spaces. - yaml.WriteString(" " + strings.TrimRight(line, " \t") + "\n") + // Simply add 6 spaces for job context indentation + yaml.WriteString(" " + line + "\n") } } } @@ -351,10 +348,8 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus continue } - // Add the line with proper indentation. Right-trim so trailing whitespace - // carried in from source step content doesn't survive into the generated - // YAML, which yamllint flags as trailing-spaces. - yaml.WriteString(" " + strings.TrimRight(line, " \t") + "\n") + // Add the line with proper indentation + yaml.WriteString(" " + line + "\n") // Check if this line starts a step with "- name:" or "- uses:" trimmed := strings.TrimSpace(line) @@ -397,7 +392,7 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus if nextTrimmed == "" { yaml.WriteString("\n") } else { - yaml.WriteString(" " + strings.TrimRight(nextLine, " \t") + "\n") + yaml.WriteString(" " + nextLine + "\n") } i++ } diff --git a/pkg/workflow/compiler_yaml_step_conversion.go b/pkg/workflow/compiler_yaml_step_conversion.go index e3bfc4e59b7..ec946c3166b 100644 --- a/pkg/workflow/compiler_yaml_step_conversion.go +++ b/pkg/workflow/compiler_yaml_step_conversion.go @@ -42,14 +42,10 @@ func ConvertStepToYAML(stepMap map[string]any) (string, error) { var result strings.Builder for _, line := range lines { - // Right-trim so trailing whitespace carried in from source step content - // (e.g. embedded shell/js/jq inside a literal block scalar) doesn't survive - // into the generated YAML, which yamllint flags as trailing-spaces. Trailing - // whitespace is insignificant for the shell/script content these steps carry. - if trimmed := strings.TrimRight(line, " \t"); strings.TrimSpace(trimmed) == "" { + if strings.TrimSpace(line) == "" { result.WriteString("\n") } else { - result.WriteString(" " + trimmed + "\n") + result.WriteString(" " + line + "\n") } } diff --git a/pkg/workflow/compiler_yaml_step_lifecycle.go b/pkg/workflow/compiler_yaml_step_lifecycle.go index bff46a0fc21..a07ac5ee03e 100644 --- a/pkg/workflow/compiler_yaml_step_lifecycle.go +++ b/pkg/workflow/compiler_yaml_step_lifecycle.go @@ -38,13 +38,10 @@ func writeStepsSection(yaml *strings.Builder, stepsYAML string) { yaml.WriteString("\n") continue } - // Emit the right-trimmed line so trailing whitespace carried in from the - // source step content (e.g. embedded shell/jq/js scripts) doesn't survive - // into the generated YAML, which yamllint flags as trailing-spaces. - if strings.HasPrefix(trimmed, " ") { - yaml.WriteString(" " + trimmed[2:] + "\n") + if strings.HasPrefix(line, " ") { + yaml.WriteString(" " + line[2:] + "\n") } else { - yaml.WriteString(" " + trimmed + "\n") + yaml.WriteString(" " + line + "\n") } } } From 46b63b4228dbe050869e50310643491f6d685464 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:06:17 +0000 Subject: [PATCH 4/9] plan: implement block-scalar-aware appendYAMLLine helper and reuse across emitters Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/dataflow-pr-discussion-dataset.lock.yml | 6 +++--- .github/workflows/deep-report.lock.yml | 6 +++--- .github/workflows/issue-monster.lock.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml index 833b821afd7..b1d3379c44b 100644 --- a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml +++ b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml @@ -688,12 +688,12 @@ jobs: # Extract discussions and normalize structure echo "$RESULT" | jq -r ' - .data.repository.discussions.nodes + .data.repository.discussions.nodes | map({ - number, + number, title, body, - createdAt, + createdAt, updatedAt, url, category: .category.name, diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 1530de732d9..5516b7c9652 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -709,12 +709,12 @@ jobs: # Extract discussions and normalize structure echo "$RESULT" | jq -r ' - .data.repository.discussions.nodes + .data.repository.discussions.nodes | map({ - number, + number, title, body, - createdAt, + createdAt, updatedAt, url, category: .category.name, diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index eab70ad7946..58cd66509fb 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -2496,8 +2496,8 @@ jobs: } // Exclude issues with open PRs from Copilot coding agent - const openCopilotPRs = issue.linkedPRs?.filter(pr => - pr.state === 'OPEN' && + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) ) || []; if (openCopilotPRs.length > 0) { From ab845fa0b0f3a5fd3ca0a27b3a68b7748d00182b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:10:10 +0000 Subject: [PATCH 5/9] feat: add block-scalar-aware appendYAMLLine helper; reuse across step emitters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces yamlBlockScalarState (update method) and appendYAMLLine in a new compiler_yaml_line_writer.go so that the logic for "trim structural lines, preserve block-scalar payload verbatim" lives in one place and is reused by: - writeStepsSection (compiler_yaml_step_lifecycle.go) - ConvertStepToYAML (compiler_yaml_step_conversion.go) - addCustomStepsAsIs + addCustomStepsWithRuntimeInsertion (compiler_yaml_runtime_setup.go) The helper trims trailing whitespace from structural YAML lines (fixing the yamllint trailing-spaces warnings) while writing block-scalar payload lines verbatim so that semantically significant whitespace — shell backslash-newline continuations, heredoc indentation, template literal spacing — is preserved. Blank lines are always emitted as bare newlines regardless of block-scalar state to avoid trailing-space violations in the generated YAML. The yamlBlockScalarState.update method reuses the blockScalarHeaderIndentForLine and countLeadingSpaces helpers from compiler_yaml_normalize.go, applying the same detection logic that normalizeBlankLines already uses. Tests added in compiler_yaml_test.go: TestYamlBlockScalarStateUpdate (6 sub-cases) and TestAppendYAMLLine (3 sub-cases). Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler_yaml_line_writer.go | 98 +++++++++++++ pkg/workflow/compiler_yaml_runtime_setup.go | 22 ++- pkg/workflow/compiler_yaml_step_conversion.go | 8 +- pkg/workflow/compiler_yaml_step_lifecycle.go | 17 ++- pkg/workflow/compiler_yaml_test.go | 136 ++++++++++++++++++ 5 files changed, 258 insertions(+), 23 deletions(-) create mode 100644 pkg/workflow/compiler_yaml_line_writer.go diff --git a/pkg/workflow/compiler_yaml_line_writer.go b/pkg/workflow/compiler_yaml_line_writer.go new file mode 100644 index 00000000000..3f343260b16 --- /dev/null +++ b/pkg/workflow/compiler_yaml_line_writer.go @@ -0,0 +1,98 @@ +package workflow + +import "strings" + +// yamlBlockScalarState tracks whether the scanner is currently inside a YAML +// literal (|) or folded (>) block scalar. It is used by appendYAMLLine to +// decide whether a line's trailing whitespace may be safely trimmed. +// +// The zero value is ready to use. Create a new instance per YAML stream and +// pass it to every appendYAMLLine call in that stream. +type yamlBlockScalarState struct { + pending bool // saw a block-scalar header; waiting for the first content line + active bool // inside a block scalar's payload + headerIndent int // leading-space count of the block-scalar header line + bodyIndent int // leading-space count of the first payload line +} + +// update advances the block-scalar state machine for sourceLine (the raw line +// as it appears in the source YAML, before any prefix or indentation adjustment +// is applied). It returns true when sourceLine is part of a block scalar's +// payload and its content must NOT be right-trimmed. +func (s *yamlBlockScalarState) update(sourceLine string) bool { + trimmed := strings.TrimRight(sourceLine, " \t") + + if s.pending || s.active { + if trimmed == "" { + // Blank lines keep the state alive without advancing it: + // inside an active scalar they are payload; inside a pending + // scalar they simply delay the first content line. + return s.active + } + lineIndent := countLeadingSpaces(sourceLine) + if s.pending { + if lineIndent <= s.headerIndent { + // No indented content followed the header; leave the scalar. + s.pending = false + // Fall through to structural handling below. + } else { + s.bodyIndent = lineIndent + s.active = true + s.pending = false + return true // first payload line + } + } + if s.active { + if lineIndent < s.bodyIndent { + // Outdented non-blank line: we have left the block scalar. + s.active = false + // Fall through to structural handling below. + } else { + return true // still inside the block scalar + } + } + } + + // Structural line: detect whether it introduces a new block scalar. + if trimmed != "" { + if headerIndent, ok := blockScalarHeaderIndentForLine(trimmed); ok { + s.pending = true + s.headerIndent = headerIndent + } + } + return false +} + +// appendYAMLLine writes content to b with the given prefix. +// +// - Blank content (empty or whitespace-only) is always written as a bare +// newline with no prefix, regardless of isBlockScalarContent, to avoid +// emitting lines that contain only spaces (yamllint trailing-spaces). +// - When isBlockScalarContent is true, non-blank content is written verbatim +// so that trailing whitespace that is semantically significant inside a +// shell script or template literal is preserved. +// - When isBlockScalarContent is false, trailing whitespace is trimmed before +// writing so that yamllint trailing-spaces warnings are suppressed. +// +// Callers obtain isBlockScalarContent by calling yamlBlockScalarState.update +// with the original source line. When the source indentation is adjusted before +// writing (e.g. a 2-space prefix is stripped in writeStepsSection), callers +// must still pass the original source line to update so that indent-based +// entry/exit detection remains accurate, and then pass the adjusted content +// here. +func appendYAMLLine(b *strings.Builder, prefix, content string, isBlockScalarContent bool) { + // Always emit blank lines as bare newlines to avoid trailing-space violations. + if strings.TrimRight(content, " \t") == "" { + b.WriteByte('\n') + return + } + if isBlockScalarContent { + b.WriteString(prefix) + b.WriteString(content) + b.WriteByte('\n') + return + } + b.WriteString(prefix) + b.WriteString(strings.TrimRight(content, " \t")) + b.WriteByte('\n') +} diff --git a/pkg/workflow/compiler_yaml_runtime_setup.go b/pkg/workflow/compiler_yaml_runtime_setup.go index 1331aa4d6cf..e7dad1f1b8d 100644 --- a/pkg/workflow/compiler_yaml_runtime_setup.go +++ b/pkg/workflow/compiler_yaml_runtime_setup.go @@ -312,15 +312,10 @@ func (c *Compiler) addCustomStepsAsIs(yaml *strings.Builder, customSteps string) // Remove "steps:" line and adjust indentation lines := strings.Split(customSteps, "\n") if len(lines) > 1 { + var bs yamlBlockScalarState for _, line := range lines[1:] { - // Skip empty lines - if strings.TrimSpace(line) == "" { - yaml.WriteString("\n") - continue - } - - // Simply add 6 spaces for job context indentation - yaml.WriteString(" " + line + "\n") + isBS := bs.update(line) + appendYAMLLine(yaml, " ", line, isBS) } } } @@ -337,9 +332,11 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus insertedRuntime := false i := 1 // Start from index 1 to skip "steps:" line + var bs yamlBlockScalarState for i < len(lines) { line := lines[i] + isBS := bs.update(line) // Skip empty lines if strings.TrimSpace(line) == "" { @@ -349,7 +346,7 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus } // Add the line with proper indentation - yaml.WriteString(" " + line + "\n") + appendYAMLLine(yaml, " ", line, isBS) // Check if this line starts a step with "- name:" or "- uses:" trimmed := strings.TrimSpace(line) @@ -389,11 +386,8 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus } // Add the line - if nextTrimmed == "" { - yaml.WriteString("\n") - } else { - yaml.WriteString(" " + nextLine + "\n") - } + nextIsBS := bs.update(nextLine) + appendYAMLLine(yaml, " ", nextLine, nextIsBS) i++ } diff --git a/pkg/workflow/compiler_yaml_step_conversion.go b/pkg/workflow/compiler_yaml_step_conversion.go index ec946c3166b..92b64468b0a 100644 --- a/pkg/workflow/compiler_yaml_step_conversion.go +++ b/pkg/workflow/compiler_yaml_step_conversion.go @@ -40,13 +40,11 @@ func ConvertStepToYAML(stepMap map[string]any) (string, error) { // Add 6 spaces to the beginning of each line to match GitHub Actions step indentation lines := strings.Split(strings.TrimSpace(yamlStr), "\n") var result strings.Builder + var bs yamlBlockScalarState for _, line := range lines { - if strings.TrimSpace(line) == "" { - result.WriteString("\n") - } else { - result.WriteString(" " + line + "\n") - } + isBS := bs.update(line) + appendYAMLLine(&result, " ", line, isBS) } stepConversionLog.Printf("Step conversion complete: %d lines generated", len(lines)) diff --git a/pkg/workflow/compiler_yaml_step_lifecycle.go b/pkg/workflow/compiler_yaml_step_lifecycle.go index a07ac5ee03e..724d76bcff1 100644 --- a/pkg/workflow/compiler_yaml_step_lifecycle.go +++ b/pkg/workflow/compiler_yaml_step_lifecycle.go @@ -32,17 +32,26 @@ func writeStepsSection(yaml *strings.Builder, stepsYAML string) { return } lines := strings.Split(stepsYAML, "\n") + var bs yamlBlockScalarState for _, line := range lines[1:] { // skip the "pre-steps:" / "pre-agent-steps:" / "post-steps:" header line - trimmed := strings.TrimRight(line, " ") - if strings.TrimSpace(trimmed) == "" { + // Update block-scalar state using the original source line (before the + // 2-space strip below) so that indent-based entry/exit detection is correct. + isBS := bs.update(line) + if strings.TrimSpace(line) == "" { yaml.WriteString("\n") continue } + // Normalise indentation: nested properties (2+ leading spaces) get an + // 8-space indent; top-level step items get a 6-space indent. + var prefix, content string if strings.HasPrefix(line, " ") { - yaml.WriteString(" " + line[2:] + "\n") + prefix = " " + content = line[2:] } else { - yaml.WriteString(" " + line + "\n") + prefix = " " + content = line } + appendYAMLLine(yaml, prefix, content, isBS) } } diff --git a/pkg/workflow/compiler_yaml_test.go b/pkg/workflow/compiler_yaml_test.go index dcd56c65522..a94faef03e7 100644 --- a/pkg/workflow/compiler_yaml_test.go +++ b/pkg/workflow/compiler_yaml_test.go @@ -1858,3 +1858,139 @@ func TestNormalizeBlankLinesPreservesBlockScalarContent(t *testing.T) { t.Fatalf("block scalar content changed after normalization\nwant: %q\ngot: %q", want, got) } } + +// ======================================== +// Tests for yamlBlockScalarState / appendYAMLLine +// ======================================== + +// TestYamlBlockScalarStateUpdate verifies that the block-scalar state machine +// correctly identifies payload lines and structural lines. +func TestYamlBlockScalarStateUpdate(t *testing.T) { + tests := []struct { + name string + lines []string + wantBS []bool // expected isBlockScalarContent for each line + }{ + { + name: "no block scalar", + lines: []string{ + "- name: foo ", + " run: echo hello ", + }, + wantBS: []bool{false, false}, + }, + { + name: "literal block scalar payload preserved", + lines: []string{ + " run: |", + " echo hello ", + " echo world\\ ", + }, + wantBS: []bool{false, true, true}, + }, + { + name: "blank line inside block scalar does not exit", + lines: []string{ + " run: |", + " line1 ", + "", + " line2 ", + }, + wantBS: []bool{false, true, true, true}, + }, + { + name: "outdented line exits block scalar", + lines: []string{ + " run: |", + " content ", + " other: value ", + }, + wantBS: []bool{false, true, false}, + }, + { + name: "folded block scalar (>) also tracked", + lines: []string{ + " script: >", + " folded content ", + }, + wantBS: []bool{false, true}, + }, + { + name: "blank line between header and content stays pending", + lines: []string{ + " run: |", + "", + " content ", + }, + wantBS: []bool{false, false, true}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var bs yamlBlockScalarState + for i, line := range tt.lines { + got := bs.update(line) + if got != tt.wantBS[i] { + t.Errorf("line %d %q: update() = %v, want %v", i, line, got, tt.wantBS[i]) + } + } + }) + } +} + +// TestAppendYAMLLine verifies that structural lines are trimmed and block-scalar +// content is preserved verbatim. +func TestAppendYAMLLine(t *testing.T) { + tests := []struct { + name string + yamlLines []string // source lines (no prefix) + prefix string + want string + }{ + { + name: "structural trailing spaces are trimmed", + yamlLines: []string{ + "- name: foo ", + " key: value ", + }, + prefix: " ", + want: " - name: foo\n key: value\n", + }, + { + name: "block scalar payload preserved verbatim", + yamlLines: []string{ + "run: |", + " echo hello ", + " echo world\\ ", + }, + prefix: " ", + want: " run: |\n echo hello \n echo world\\ \n", + }, + { + name: "blank lines always bare newlines", + yamlLines: []string{ + "run: |", + " line1 ", + "", + " line2 ", + }, + prefix: " ", + want: " run: |\n line1 \n\n line2 \n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b strings.Builder + var bs yamlBlockScalarState + for _, line := range tt.yamlLines { + isBS := bs.update(line) + appendYAMLLine(&b, tt.prefix, line, isBS) + } + if got := b.String(); got != tt.want { + t.Errorf("appendYAMLLine output mismatch\ngot: %q\nwant: %q", got, tt.want) + } + }) + } +} From 35817dc4ad8d758af51bb04a90d0905dce21ae90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:15:02 +0000 Subject: [PATCH 6/9] refactor: rename bs to blockScalarState for readability Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler_yaml_runtime_setup.go | 10 +++++----- pkg/workflow/compiler_yaml_step_conversion.go | 4 ++-- pkg/workflow/compiler_yaml_step_lifecycle.go | 4 ++-- pkg/workflow/compiler_yaml_test.go | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/workflow/compiler_yaml_runtime_setup.go b/pkg/workflow/compiler_yaml_runtime_setup.go index e7dad1f1b8d..adbadd6747d 100644 --- a/pkg/workflow/compiler_yaml_runtime_setup.go +++ b/pkg/workflow/compiler_yaml_runtime_setup.go @@ -312,9 +312,9 @@ func (c *Compiler) addCustomStepsAsIs(yaml *strings.Builder, customSteps string) // Remove "steps:" line and adjust indentation lines := strings.Split(customSteps, "\n") if len(lines) > 1 { - var bs yamlBlockScalarState + var blockScalarState yamlBlockScalarState for _, line := range lines[1:] { - isBS := bs.update(line) + isBS := blockScalarState.update(line) appendYAMLLine(yaml, " ", line, isBS) } } @@ -332,11 +332,11 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus insertedRuntime := false i := 1 // Start from index 1 to skip "steps:" line - var bs yamlBlockScalarState + var blockScalarState yamlBlockScalarState for i < len(lines) { line := lines[i] - isBS := bs.update(line) + isBS := blockScalarState.update(line) // Skip empty lines if strings.TrimSpace(line) == "" { @@ -386,7 +386,7 @@ func (c *Compiler) addCustomStepsWithRuntimeInsertion(yaml *strings.Builder, cus } // Add the line - nextIsBS := bs.update(nextLine) + nextIsBS := blockScalarState.update(nextLine) appendYAMLLine(yaml, " ", nextLine, nextIsBS) i++ } diff --git a/pkg/workflow/compiler_yaml_step_conversion.go b/pkg/workflow/compiler_yaml_step_conversion.go index 92b64468b0a..8db3c540789 100644 --- a/pkg/workflow/compiler_yaml_step_conversion.go +++ b/pkg/workflow/compiler_yaml_step_conversion.go @@ -40,10 +40,10 @@ func ConvertStepToYAML(stepMap map[string]any) (string, error) { // Add 6 spaces to the beginning of each line to match GitHub Actions step indentation lines := strings.Split(strings.TrimSpace(yamlStr), "\n") var result strings.Builder - var bs yamlBlockScalarState + var blockScalarState yamlBlockScalarState for _, line := range lines { - isBS := bs.update(line) + isBS := blockScalarState.update(line) appendYAMLLine(&result, " ", line, isBS) } diff --git a/pkg/workflow/compiler_yaml_step_lifecycle.go b/pkg/workflow/compiler_yaml_step_lifecycle.go index 724d76bcff1..4a98e8f8817 100644 --- a/pkg/workflow/compiler_yaml_step_lifecycle.go +++ b/pkg/workflow/compiler_yaml_step_lifecycle.go @@ -32,11 +32,11 @@ func writeStepsSection(yaml *strings.Builder, stepsYAML string) { return } lines := strings.Split(stepsYAML, "\n") - var bs yamlBlockScalarState + var blockScalarState yamlBlockScalarState for _, line := range lines[1:] { // skip the "pre-steps:" / "pre-agent-steps:" / "post-steps:" header line // Update block-scalar state using the original source line (before the // 2-space strip below) so that indent-based entry/exit detection is correct. - isBS := bs.update(line) + isBS := blockScalarState.update(line) if strings.TrimSpace(line) == "" { yaml.WriteString("\n") continue diff --git a/pkg/workflow/compiler_yaml_test.go b/pkg/workflow/compiler_yaml_test.go index a94faef03e7..974a6f3277c 100644 --- a/pkg/workflow/compiler_yaml_test.go +++ b/pkg/workflow/compiler_yaml_test.go @@ -1928,9 +1928,9 @@ func TestYamlBlockScalarStateUpdate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var bs yamlBlockScalarState + var blockScalarState yamlBlockScalarState for i, line := range tt.lines { - got := bs.update(line) + got := blockScalarState.update(line) if got != tt.wantBS[i] { t.Errorf("line %d %q: update() = %v, want %v", i, line, got, tt.wantBS[i]) } @@ -1983,9 +1983,9 @@ func TestAppendYAMLLine(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b strings.Builder - var bs yamlBlockScalarState + var blockScalarState yamlBlockScalarState for _, line := range tt.yamlLines { - isBS := bs.update(line) + isBS := blockScalarState.update(line) appendYAMLLine(&b, tt.prefix, line, isBS) } if got := b.String(); got != tt.want { From a3b85e4af05719a59e17143d793d8922f248f77b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:37:06 +0000 Subject: [PATCH 7/9] fix: remove trailing whitespace from .md source files to fix yamllint warnings in lock files Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/dataflow-pr-discussion-dataset.lock.yml | 8 ++++---- .github/workflows/deep-report.lock.yml | 8 ++++---- .github/workflows/issue-monster.lock.yml | 6 +++--- .github/workflows/issue-monster.md | 4 ++-- .github/workflows/shared/discussions-data-fetch.md | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml index b1d3379c44b..83c14eea61e 100644 --- a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml +++ b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c1d27014e481f74fb85facf91c39359363dd47029f2ff9e8dd58da283e578c1e","body_hash":"e5fad0d0dd9d970f63e21ae0f6596e7ead355fff1738bd1f924d6461bc27887c","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.71"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"cbdd289a6d9258b11c33d8d52c475584519e0b91fb42de2310cf87e2f2a6ca32","body_hash":"70341e53871b3268cc556e830b07973a73aea98d4a690fa199321c6fa8e22e70","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.71"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"safedep/pmg","sha":"46cc70db535107183c9e752bb55d1d5c5f1a9290","version":"v1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37","digest":"sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37","digest":"sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.37","digest":"sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.37@sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37","digest":"sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -688,12 +688,12 @@ jobs: # Extract discussions and normalize structure echo "$RESULT" | jq -r ' - .data.repository.discussions.nodes + .data.repository.discussions.nodes | map({ - number, + number, title, body, - createdAt, + createdAt, updatedAt, url, category: .category.name, diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 5516b7c9652..bac5a6d3169 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9c28572dac37ac74ade681d41128f544c51a9d0588161fd9e97634bfe5b516f3","body_hash":"e91d8be8d4d836c07b45123140832d2bf4973ca5fd06e1d720f615472b171838","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.214"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2b251c27f6d5e535332d97eb3e28f0151d3df0cf5b1e03b3a3dce5581d8b034f","body_hash":"8f8f453268abdc2aac93a9c79b85e4859753b40809dab98f60dcb40bc32aa2a9","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.214"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"53b7df96c91f9c12dcc8a07bcb9ccacbed38856a","version":"v7.3.0"},{"repo":"docker/setup-buildx-action","sha":"bb05f3f5519dd87d3ba754cc423b652a5edd6d2c","version":"v4.2.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37","digest":"sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37","digest":"sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.37","digest":"sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.37@sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37","digest":"sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -709,12 +709,12 @@ jobs: # Extract discussions and normalize structure echo "$RESULT" | jq -r ' - .data.repository.discussions.nodes + .data.repository.discussions.nodes | map({ - number, + number, title, body, - createdAt, + createdAt, updatedAt, url, category: .category.name, diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 58cd66509fb..cc47c6bffa6 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"97ff7de334c89ca00a39a9aaa8e0d4a3d1270f153cff0cf0bd3c7d1c7c25527a","body_hash":"bfda3055b12ee8a51b62d5da483fe2adead74c59e3c8db50292dbe457f7cc7bd","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.10"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a0a11d4ec3b95981ad6c8ed204e5405794c47d324c973a53b1377f78e7d52a78","body_hash":"bfda3055b12ee8a51b62d5da483fe2adead74c59e3c8db50292dbe457f7cc7bd","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.10"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37","digest":"sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37","digest":"sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.37","digest":"sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.37@sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37","digest":"sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -2496,8 +2496,8 @@ jobs: } // Exclude issues with open PRs from Copilot coding agent - const openCopilotPRs = issue.linkedPRs?.filter(pr => - pr.state === 'OPEN' && + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) ) || []; if (openCopilotPRs.length > 0) { diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 9f4a0e9cbcd..5cfb3d9edc5 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -284,8 +284,8 @@ on: } // Exclude issues with open PRs from Copilot coding agent - const openCopilotPRs = issue.linkedPRs?.filter(pr => - pr.state === 'OPEN' && + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) ) || []; if (openCopilotPRs.length > 0) { diff --git a/.github/workflows/shared/discussions-data-fetch.md b/.github/workflows/shared/discussions-data-fetch.md index a12bd94864c..cc0f9156b15 100644 --- a/.github/workflows/shared/discussions-data-fetch.md +++ b/.github/workflows/shared/discussions-data-fetch.md @@ -97,12 +97,12 @@ steps: # Extract discussions and normalize structure echo "$RESULT" | jq -r ' - .data.repository.discussions.nodes + .data.repository.discussions.nodes | map({ - number, + number, title, body, - createdAt, + createdAt, updatedAt, url, category: .category.name, @@ -180,7 +180,7 @@ This shared component fetches open discussions from the repository, with intelli - **Cache Key**: `discussions-data` for workflow-level sharing - **Cache Files**: Stored with today's date in the filename (e.g., `discussions-2024-11-18.json`) - **Cache Location**: `/tmp/gh-aw/cache-memory/` -- **Cache Benefits**: +- **Cache Benefits**: - Multiple workflows running on the same day share the same discussions data - Reduces GitHub API rate limit usage - Faster workflow execution after first fetch of the day From 36bd61286b3b02ac685bf5e1e19cec22d710938b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:59:14 +0000 Subject: [PATCH 8/9] test: add TestWriteStepsSection and TestAddCustomStepsAsIsTrimsStructuralTrailingSpaces Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler_yaml_test.go | 95 ++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/pkg/workflow/compiler_yaml_test.go b/pkg/workflow/compiler_yaml_test.go index 974a6f3277c..b83a0a23236 100644 --- a/pkg/workflow/compiler_yaml_test.go +++ b/pkg/workflow/compiler_yaml_test.go @@ -1994,3 +1994,98 @@ func TestAppendYAMLLine(t *testing.T) { }) } } + +// ======================================== +// Tests for writeStepsSection +// ======================================== + +// TestWriteStepsSection verifies that writeStepsSection trims trailing whitespace +// from structural YAML lines while preserving block-scalar payload verbatim. +func TestWriteStepsSection(t *testing.T) { + tests := []struct { + name string + stepsYAML string + wantLines []string // substrings that must appear in the output + wantNot []string // substrings that must NOT appear in the output + }{ + { + name: "structural trailing spaces trimmed", + stepsYAML: "pre-steps:\n- name: My Step \n run: echo hi \n", + wantLines: []string{"- name: My Step\n", "run: echo hi\n"}, + wantNot: []string{"My Step ", "echo hi "}, + }, + { + name: "block scalar payload preserved verbatim", + stepsYAML: "pre-steps:\n- name: Script\n run: |\n echo hello \n echo world\\ \n", + wantLines: []string{"echo hello ", "echo world\\ "}, + }, + { + name: "blank lines emitted as bare newlines", + stepsYAML: "pre-steps:\n- name: A\n run: echo a\n\n- name: B\n run: echo b\n", + wantLines: []string{"- name: A", "- name: B"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b strings.Builder + writeStepsSection(&b, tt.stepsYAML) + got := b.String() + + for _, want := range tt.wantLines { + if !strings.Contains(got, want) { + t.Errorf("expected output to contain %q\ngot: %q", want, got) + } + } + for _, notWant := range tt.wantNot { + if strings.Contains(got, notWant) { + t.Errorf("expected output NOT to contain %q\ngot: %q", notWant, got) + } + } + }) + } +} + +// TestAddCustomStepsAsIsTrimsStructuralTrailingSpaces verifies that addCustomStepsAsIs +// trims trailing whitespace from structural YAML lines but preserves block-scalar payload. +func TestAddCustomStepsAsIsTrimsStructuralTrailingSpaces(t *testing.T) { + compiler := NewCompiler() + + tests := []struct { + name string + customSteps string + wantLines []string // substrings that must appear + wantNot []string // substrings that must NOT appear + }{ + { + name: "structural trailing spaces are trimmed", + customSteps: "steps:\n- name: My Step \n uses: actions/checkout@v4 \n", + wantLines: []string{"- name: My Step\n", "uses: actions/checkout@v4\n"}, + wantNot: []string{"My Step ", "checkout@v4 "}, + }, + { + name: "block scalar run content preserved verbatim", + customSteps: "steps:\n- name: Script\n run: |\n echo trailing \n echo bs\\ \n", + wantLines: []string{"echo trailing ", "echo bs\\ "}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b strings.Builder + compiler.addCustomStepsAsIs(&b, tt.customSteps) + got := b.String() + + for _, want := range tt.wantLines { + if !strings.Contains(got, want) { + t.Errorf("expected output to contain %q\ngot: %q", want, got) + } + } + for _, notWant := range tt.wantNot { + if strings.Contains(got, notWant) { + t.Errorf("expected output NOT to contain %q\ngot: %q", notWant, got) + } + } + }) + } +} From 4bcbb2c84e7855ef60c668fba15457a36586794d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 06:00:52 +0000 Subject: [PATCH 9/9] test: clarify backslash-space test cases with comments explaining shell continuation risk Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler_yaml_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/compiler_yaml_test.go b/pkg/workflow/compiler_yaml_test.go index b83a0a23236..130f6e2996c 100644 --- a/pkg/workflow/compiler_yaml_test.go +++ b/pkg/workflow/compiler_yaml_test.go @@ -2015,7 +2015,11 @@ func TestWriteStepsSection(t *testing.T) { wantNot: []string{"My Step ", "echo hi "}, }, { - name: "block scalar payload preserved verbatim", + name: "block scalar payload preserved verbatim", + // `\\ ` in the Go string literal represents a literal backslash followed by + // two trailing spaces in the actual content. This is the critical case: a shell + // line ending in `\ ` (backslash + spaces) must not be trimmed because the + // spaces prevent the backslash from acting as a line-continuation character. stepsYAML: "pre-steps:\n- name: Script\n run: |\n echo hello \n echo world\\ \n", wantLines: []string{"echo hello ", "echo world\\ "}, }, @@ -2064,7 +2068,10 @@ func TestAddCustomStepsAsIsTrimsStructuralTrailingSpaces(t *testing.T) { wantNot: []string{"My Step ", "checkout@v4 "}, }, { - name: "block scalar run content preserved verbatim", + name: "block scalar run content preserved verbatim", + // `\\ ` in the Go string literal represents a literal backslash followed by + // two trailing spaces. Trimming would change `\ ` → `\`, flipping the shell + // backslash-newline continuation semantics — so payload must be kept verbatim. customSteps: "steps:\n- name: Script\n run: |\n echo trailing \n echo bs\\ \n", wantLines: []string{"echo trailing ", "echo bs\\ "}, },