Skip to content

Fix deferred runtime setup for unnamed custom checkout steps#47545

Merged
pelikhan merged 9 commits into
mainfrom
copilot/fix-deferred-runtime-setup
Jul 23, 2026
Merged

Fix deferred runtime setup for unnamed custom checkout steps#47545
pelikhan merged 9 commits into
mainfrom
copilot/fix-deferred-runtime-setup

Conversation

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Custom checkout shorthand (- uses: actions/checkout@...) could trigger runtime deferral during detection but be missed during insertion, dropping required runtime setup from the generated workflow. This only surfaced later at execution time and made named vs unnamed checkout forms behave differently.

  • Unify checkout detection

    • Route checkout recognition through a shared helper that identifies actions/checkout exactly instead of relying on substring scans.
    • Support both full steps: YAML and bare custom-step snippets used by older call sites.
    • Avoid false positives from comments, run: text, or unrelated actions whose names contain checkout.
  • Insert runtime setup after the first applicable checkout

    • Change custom-step runtime insertion to use the first detected checkout step index.
    • Recognize checkout on the current - uses: line, not only on following uses: lines.
    • Preserve existing semantics: insert generated runtime setup once, immediately after the first applicable custom checkout, while keeping later custom steps in order.
  • Expand regression coverage

    • Cover named and unnamed checkout forms, including with: options.
    • Cover checkout as the first custom step, after a deterministic step, and with multiple checkout steps.
    • Cover customized runtime setup actions to ensure existing deduplication/preservation behavior remains intact.
    • Tighten checkout detection tests to reject non-checkout lookalikes.

Example of the previously broken form now handled equivalently to a named checkout step:

steps:
  - uses: actions/checkout@v6
    with:
      fetch-depth: 0
      persist-credentials: false
  - name: Prepare context
    run: echo ready

Generated ordering remains:

- uses: actions/checkout@...
- name: Setup Node.js
- name: Prepare context

Generated by 👨‍🍳 PR Sous Chef · gpt54 19.7 AIC · ⌖ 8.4 AIC · ⊞ 7K ·
Comment /souschef to run again


$

Generated by 👨‍🍳 PR Sous Chef · gpt54 11.6 AIC · ⌖ 7.58 AIC · ⊞ 7K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix unnamed custom checkout steps dropping deferred runtime setup Fix deferred runtime setup for unnamed custom checkout steps Jul 23, 2026
Copilot AI requested a review from pelikhan July 23, 2026 11:14
@pelikhan
pelikhan marked this pull request as ready for review July 23, 2026 11:37
Copilot AI review requested due to automatic review settings July 23, 2026 11:37
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes deferred runtime setup after unnamed custom checkout steps.

Changes:

  • Adds shared, exact checkout-action detection.
  • Inserts runtime setup after the first checkout.
  • Expands ordering and false-positive regression tests.
Show a summary per file
File Description
pkg/workflow/compiler_workflow_helpers.go Adds structured checkout detection; bare snippets remain unsupported.
pkg/workflow/compiler_yaml_runtime_setup.go Inserts setup using the detected checkout index.
pkg/workflow/permissions_parser_test.go Tightens false-positive expectations.
pkg/workflow/compiler_yaml_main_job_test.go Updates unnamed-checkout insertion coverage.
pkg/workflow/checkout_runtime_order_test.go Adds end-to-end ordering variants.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread pkg/workflow/compiler_workflow_helpers.go
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100 — Excellent

Analyzed 8 test scenarios: 7 behavioral contracts, 1 implementation detail, 0 violations.

📊 Metrics (8 analyzed tests)
Metric Value
Analyzed 8 (Go unit: 8, JS: 0)
✅ Design 7 (87.5%)
⚠️ Implementation 1 (12.5%)
Edge/error coverage 8 (100%)
Duplicate clusters 0
Inflation Yes (9.1:1 for new file)
🚨 Violations 0
Build tags ✅ Present
Forbidden mocks ✅ None
Test File Classification Issues
TestCheckoutRuntimeOrderInCustomSteps checkout_runtime_order_test.go:19 behavioral_contract None — 8+ assertions with descriptive context
TestCheckoutRuntimeOrderInCustomStepVariants (5 subtests) checkout_runtime_order_test.go:223 behavioral_contract None — table-driven with marker ordering
TestCheckoutFirstWhenNoCustomSteps checkout_runtime_order_test.go:345 behavioral_contract None — negative assertions validate redundancy
TestContainsCheckout (5 cases fixed) permissions_parser_test.go:324+ implementation_test None — corrected false positives
TestAddCustomStepsWithRuntimeInsertion (1 case) compiler_yaml_main_job_test.go:436 design_test None — reflects production fix

Test Coverage Strength

Strong behavioral contracts: All three new Go tests validate critical workflow step ordering invariants — checkout position, runtime setup placement, and deferred runtime setup injection. These are high-value regression tests that verify user-visible behavior (step ordering in compiled workflows).

Comprehensive edge cases:

  • Unnamed - uses: actions/checkout@v5 detection (without name:)
  • Checkout after deterministic steps
  • Multiple checkout steps (runtime inserted after first only)
  • Custom runtime setup preservation (no duplication)
  • No custom steps → automatic checkout placement
  • Negative assertions: .github checkout NOT present when full checkout exists

Error assertions abundant: 45+ assertions across the file; most use t.Errorf with descriptive failure messages (e.g., "Eighth step should be 'Setup Node.js' (runtime setup after checkout), got '%s'")

No mocking: All tests use actual workflow compilation → file I/O → YAML parsing. This is genuine integration testing of the compiler, validating real end-to-end behavior.

Why This Test Score is Trustworthy

  1. Design contracts (87.5%): Tests verify what the system does (step ordering), not how it does it.
  2. No false comfort: High assertion count reflects complexity of the fix (deferred setup, unnamed checkout detection), not test inflation.
  3. Inflation explained: New test file (210 lines test, 23 lines production change) is appropriate for a significant behavioral change. The ratio reflects the scope of the fix, not low-value tests.
  4. Build compliance: All tests have required //go:build !integration tag; no mocking violations.

Verdict

Passed. 12.5% implementation tests (threshold: 30%). This PR strengthens test coverage for a critical workflow compiler behavior change with high-value, design-focused tests.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 26.2 AIC · ⌖ 9.44 AIC · ⊞ 7.1K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 82/100. 12.5% implementation tests (threshold: 30%). Tests verify critical behavioral contracts for workflow step ordering with comprehensive edge case coverage and strong assertion quality.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (259 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/47545-yaml-parsed-checkout-detection-for-workflow-compiler.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-47545: YAML-Parsed Checkout Detection for Workflow Compiler

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 48.4 AIC · ⌖ 10.2 AIC · ⊞ 4.3K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Fix deferred runtime setup for unnamed custom checkout steps

This is a clean, well-reasoned fix. The core change — replacing fragile substring scanning with proper YAML parsing via findFirstCheckoutStepIndex — eliminates a class of false positives and correctly handles unnamed checkout steps (bare - uses: actions/checkout@... without a name: field).

Key observations:

  • isCheckoutActionReference is correctly strict: only actions/checkout or actions/checkout@* match, rejecting my-actions/checkout and comment/run text.
  • The step-index approach in addCustomStepsWithRuntimeInsertion correctly handles multi-step lookahead by pre-computing checkoutStepIndex from parsed YAML, then matching it against the incrementing currentStepIndex as lines are scanned.
  • Malformed YAML now causes ContainsCheckout to return false (previously it matched by substring). This is intentional, explicitly tested, and the right behavior for a safety-first detection.
  • Test coverage is thorough: named, unnamed, with options, after a deterministic step, multiple checkouts, and custom runtime deduplication.

No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 46 AIC · ⌖ 4.55 AIC · ⊞ 5K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on three issues.

📋 Key Themes & Highlights

Issues Found

  1. Detection/insertion double-parse with sanitization gap (compiler_yaml_runtime_setup.go:327): findFirstCheckoutStepIndex is called both before (via ContainsCheckout) and inside addCustomStepsWithRuntimeInsertion. The inner call happens after sanitizeAndWarnCustomSteps, so a ${{ }} expression in a uses: field could cause the outer detection and inner insertion to operate on different strings, reinstating the detection/insertion mismatch this PR is fixing.

  2. Inner loop block-scalar blindspot (compiler_yaml_runtime_setup.go:378): The inner loop that drains checkout step lines stops on any - -prefixed line at the step indent, but does not track blockScalarState. A sparse-checkout: | value containing lines like - src would prematurely terminate the checkout step drain and insert runtime setup mid-step.

  3. assertOrderedMarkers position arithmetic bug (checkout_runtime_order_test.go:562): The helper uses relative offsets that produce incorrect absolute positions when index == 0, meaning it may pass when markers appear in the wrong order in edge cases.

Positive Highlights

  • ✅ Excellent decision to route all detection through a single YAML-parsing helper, eliminating the substring scan false-positives completely.
  • ✅ Pre-computing checkoutStepIndex via the same codepath as detection is exactly the right fix for the original bug.
  • ✅ Comprehensive test matrix in TestCheckoutRuntimeOrderInCustomStepVariants covering named/unnamed checkout, insertion ordering, multiple checkouts, and runtime deduplication.
  • isCheckoutActionReference correctly handles quoted uses values and is case-insensitive — solid defensive coding.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 55.8 AIC · ⌖ 5.14 AIC · ⊞ 6.7K
Comment /matt to run again

Comment thread pkg/workflow/compiler_yaml_runtime_setup.go
Comment thread pkg/workflow/compiler_yaml_runtime_setup.go Outdated
Comment thread pkg/workflow/compiler_workflow_helpers.go Outdated
Comment thread pkg/workflow/checkout_runtime_order_test.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — three correctness bugs need fixing before merge

The YAML-parsing-based checkout detection is a solid improvement over substring scanning. However two related bugs in the line-level insertion loop and one bug in the new test helper undermine the fix.

Blocking issues (3)

1. Block scalar content increments step counter (high)isStepStart in the outer insertion loop is evaluated without checking isBS. Any run: | step whose body contains a - -prefixed line at the step-list indent will falsely increment currentStepIndex, making the runtime insert at the wrong step or not at all.

2. Inner checkout-copy loop fires on block scalar payload (high) — the inner loop that copies the checkout step's remaining lines also lacks a block-scalar guard on the boundary check. A checkout step with a run: | sub-field that contains - -prefixed lines could be silently truncated mid-copy.

3. Off-by-two byte slice in extractAgentJobSection (medium)agentJobStart+10 skips 10 bytes past " agent:" (8 bytes), corrupting both remainingContent and the final return slice. All new tests in TestCheckoutRuntimeOrderInCustomStepVariants run over a mis-sliced section, so passing tests don't prove what they claim.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 76.5 AIC · ⌖ 5 AIC · ⊞ 5.7K
Comment /review to run again

Comments that could not be inline-anchored

pkg/workflow/compiler_yaml_runtime_setup.go:365

Block scalar content not guarded from step-index counting: isStepStart is evaluated without checking isBS returned by blockScalarState.update on the same iteration. A run: | step whose body contains a line starting with - at the same indent as the step list will falsely increment currentStepIndex, causing runtime insertion to target the wrong step or be skipped entirely.

<details>
<summary>💡 Suggested fix</summary>

Gate the step-start check on !isBS:

isStepStart :=</details>

<details><summary>pkg/workflow/compiler_yaml_runtime_setup.go:378</summary>

**Inner checkout-copy loop does not guard block scalar content at step boundary**: the inner loop that copies the checkout step&#39;s remaining lines checks `strings.HasPrefix(nextTrimmed, &quot;- &quot;) &amp;&amp; nextIndent == stepIndent` to detect the next step&#39;s start, but never tracks block scalar state for the lines it processes. A checkout step with a `run: |` sub-field whose content includes `- `-prefixed lines at the step list indent will prematurely terminate the copy, silently truncating the checkout ste</details>

<details><summary>pkg/workflow/checkout_runtime_order_test.go:194</summary>

**Off-by-two byte slice corrupts the extracted agent-job section**: `&quot;  agent:&quot;` is 8 bytes, but `agentJobStart+10` skips 10 bytes, silently discarding the first 2 characters of the value following the key. This propagates into the `nextJobStart` reconstruction at line 211, making the returned section boundaries consistently wrong and causing order-marker assertions to search over a mis-sliced string.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Use the correct key length or `strings.Index`</details>

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Unresolved review threads (newest first):

Generated by 👨‍🍳 PR Sous Chef · gpt54 19.7 AIC · ⌖ 8.4 AIC · ⊞ 7K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 23, 2026 12:15
… assertOrderedMarkers fix, comments

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 PR Triage — §30008120537

Field Value
Category bug
Risk 🟡 medium
Score 78 / 100
Breakdown Impact 38 · Urgency 25 · Quality 15
Action 🚀 fast_track
CI unknown (no checks yet)
Review Commented by Copilot reviewer + swe-agent

Notes: Fixes runtime setup being silently dropped for unnamed custom checkout steps — correctness bug in the compiler. 7 files, 379+/43−. Multiple swe-agent comment cycles indicate iteration; check if resolved.

Generated by 🔧 PR Triage Agent · sonnet46 54 AIC · ⌖ 5.54 AIC · ⊞ 5.7K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Unresolved review threads (newest first):

Several threads already have a follow-up from copilot-swe-agent; please finish the remaining fixes, resolve the addressed threads, and rerun checks.

Generated by 👨‍🍳 PR Sous Chef · gpt54 35.1 AIC · ⌖ 7.07 AIC · ⊞ 7K ·
Comment /souschef to run again

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Unresolved review threads (newest first):...

All five review threads have been addressed in commits 689166e and abf5655. Here's the status:

Thread r3637791417 (bare-sequence fallback in findFirstCheckoutStepIndex): Fixed — findFirstCheckoutStepIndex now tries the wrapped steps: form first, then falls back to unmarshalling as []map[string]any. Bare-sequence test cases were added to TestContainsCheckout.

Thread r3637841491 (double ContainsCheckout call / sanitization divergence): Fixed — added a comment in prepareRuntimeSetupAndCheckoutInfo explaining why the pre-sanitization gate is safe: sanitizeCustomStepsYAML only rewrites ${{ }} expressions inside run: fields and never touches uses: values, so detection results are identical before and after sanitization.

Thread r3637841508 (block scalar false-positive in inner-loop step-boundary check): Fixed — IsInPayload() was added to yamlBlockScalarState and the inner-loop break condition is now guarded by !blockScalarState.IsInPayload().

Thread r3637841517 (malformed YAML policy undocumented): Fixed — findFirstCheckoutStepIndex godoc documents the deliberate (0, false) policy for unparseable input; a cross-reference comment was added to the existing test case, and a dedicated "malformed YAML — explicit parse-error path" test case was added.

Thread r3637841530 (assertOrderedMarkers off-by-one): Fixed — now uses cursor += idx + len(marker) to advance past the entire matched text.

Local validation: make fmt, make agent-report-progress-no-test, and TEST_UNIT_RUN_FULL=1 make test-unit all pass.


  • ✅ Reviews — all five threads addressed in code and replied to; threads marked outdated (3–5) reflect code that was superseded by the pushed fixes
  • ✅ Checks (local) — make fmt, lint, and full unit test suite pass
  • ❓ Checks (CI) — stale after agent push; needs human re-trigger. Prior failures: fixed in commits 689166e/abf5655
  • ✅ Mergeable — PR is open, not draft

Hand-off: CI must be re-triggered by a maintainer (close/reopen PR, workflow_dispatch, or a push) before merge.

@pelikhan

Copy link
Copy Markdown
Collaborator

…eference

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot review https://github.com/github/gh-aw/actions/runs/30006339336/job/89238665754

The CI failure was tolowerequalfold linting isCheckoutActionReference in compiler_workflow_helpers.go:69strings.ToLower(x) == y must be strings.EqualFold. Fixed in the latest commit by replacing the equality check with strings.EqualFold(trimmed, "actions/checkout") while keeping strings.ToLower only for the HasPrefix branch (which the linter doesn't flag). All tests and lint pass locally.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Unresolved review threads (newest first):

Failed checks:

Generated by 👨‍🍳 PR Sous Chef · gpt54 11.6 AIC · ⌖ 7.58 AIC · ⊞ 7K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Unresolved review threads (newest first):...

The lint-go failure was already fixed in the previous commit (0eeec43) — strings.EqualFold replaces strings.ToLower(x) == y in isCheckoutActionReference. Additionally, daily-team-evolution-insights.lock.yml was out of sync after the merge with main; that's now committed.

All five review threads were addressed in earlier commits:

  • r3637791417: bare-sequence fallback in findFirstCheckoutStepIndex
  • r3637841491: pre-sanitization gate comment ✅
  • r3637841508: IsInPayload() block-scalar guard ✅
  • r3637841517: malformed YAML godoc + test ✅
  • r3637841530: assertOrderedMarkers off-by-one ✅

Local validation (make fmt, make agent-report-progress-no-test, TEST_UNIT_RUN_FULL=1 make test-unit) all pass. CI needs a human re-trigger.

@pelikhan
pelikhan merged commit 5810673 into main Jul 23, 2026
23 checks passed
@pelikhan
pelikhan deleted the copilot/fix-deferred-runtime-setup branch July 23, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unnamed custom checkout steps drop deferred runtime setup

4 participants