Skip to content

ci: parallel functional tests with dynamic matrix strategy - #148

Merged
ralphbean merged 9 commits into
mainfrom
ci/matrix-eval-strategy
Jul 13, 2026
Merged

ci: parallel functional tests with dynamic matrix strategy#148
ralphbean merged 9 commits into
mainfrom
ci/matrix-eval-strategy

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

  • Restructures functional-tests workflow from a single sequential job into a 4-job pipeline: gatedetectfunctional-tests (matrix) → functional-tests-complete (roll-up)
  • The detect job runs select-eval-agents.sh (from ci: dynamically select eval agents from changed files #145) and outputs a JSON array of agents to test
  • Each agent gets its own matrix leg with independent setup and execution, running in parallel
  • functional-tests-complete is a stable roll-up job name suitable for branch protection required checks — it passes when all matrix legs pass (or when no agents need testing) and fails if any leg fails

Supersedes #145 (includes the select-eval-agents.sh script and tests from that PR).

Why matrix over sequential loop?

  • True parallelism — triage and review run simultaneously on separate runners
  • Independent failure — triage failing doesn't block review (or vice versa) with fail-fast: false
  • Clean logs — each agent gets its own job with its own log stream
  • Required checks — single stable job name (functional-tests-complete) works with branch protection regardless of which/how many agents are in the matrix

Test plan

  • 15 unit tests for select-eval-agents.sh pass
  • Workflow YAML validates
  • Verified agent selection against real repo harness files
  • CI runs on this PR

🤖 Generated with Claude Code

@ralphbean
ralphbean requested a review from a team as a code owner July 13, 2026 11:30
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 11:31 AM UTC · Ended 11:38 AM UTC
Commit: bd28f41 · View workflow run →

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

CI: parallel functional tests via dynamic agent matrix + roll-up check

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Select functional test agents dynamically from PR file changes and harness references.
• Run one parallel matrix job per agent with fail-fast disabled.
• Add a stable roll-up check for branch protection and a new review-agent eval case.
Diagram

graph TD
  A["gate job"] --> B["detect job"] --> C[["select-eval-agents.sh"]] --> D["functional-tests (matrix)"] --> E[["eval/run-functional.sh"]] --> F["upload artifacts"] --> G["functional-tests-complete"]
  B --> H{{"GitHub API (gh)"}}
  subgraph Legend
    direction LR
    _job["Job"] ~~~ _script[["Script"]] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use paths-filter action to drive matrix
  • ➕ Less custom bash to maintain
  • ➕ Well-known action with community support
  • ➖ Harder to express harness-driven dependency mapping (skills/plugins/host_files/etc.)
  • ➖ Still needs bespoke logic for 'eval config exists' and roll-up semantics
2. Single job with internal parallelism (GNU parallel/xargs)
  • ➕ Simpler workflow graph and fewer jobs
  • ➕ Avoids matrix JSON plumbing
  • ➖ Less clean logs per agent and harder debugging
  • ➖ Branch protection required-check naming becomes awkward
  • ➖ Failures can interfere unless carefully isolated
3. Reusable workflow called once per agent (workflow_call)
  • ➕ Encapsulates per-agent setup; reduces duplication across workflows
  • ➕ Easier to reuse in other pipelines
  • ➖ More indirection during debugging
  • ➖ Still requires dynamic dispatch/matrix at the caller level

Recommendation: The PR’s approach (detect→dynamic matrix→stable roll-up) is the best fit for true parallelism, clean per-agent logs, and a single branch-protection check name. The custom selector script is justified because the selection criteria are derived from harness YAML references rather than simple path globs.

Files changed (10) +846 / -41

Enhancement (2) +95 / -1
select-eval-agents.shSelect functional-test agents from changed files and harness refs +90/-0

Select functional-test agents from changed files and harness refs

• Adds a script that reads changed file paths from stdin, parses harness/*.yaml with yq to extract referenced paths, and outputs the set of agents whose evals should run. Only agents with eval/<agent>/eval.yaml are eligible, and selection includes harness changes, eval/<agent>/ changes, exact matches, and directory-prefix matches.

.github/scripts/select-eval-agents.sh

run-fullsend.shExport PR_NUMBER and REPO_FULL_NAME for pull_request fixtures +5/-1

Export PR_NUMBER and REPO_FULL_NAME for pull_request fixtures

• Extends pull_request fixture env generation to export PR_NUMBER and REPO_FULL_NAME alongside GITHUB_PR_URL. This supports downstream scripts that require PR identity information during review runs.

eval/scripts/run-fullsend.sh

Tests (6) +476 / -0
select-eval-agents-test.shAdd bash test suite for agent-selection script +310/-0

Add bash test suite for agent-selection script

• Introduces a fixture-based bash test runner that builds minimal harness/eval layouts and asserts agent selection behavior across harness, env, skills/plugins, shared references, and variable path cases. Validates that agents without eval configs are never selected.

.github/scripts/select-eval-agents-test.sh

annotations.yamlDefine expected outcomes for review-agent happy-path case +33/-0

Define expected outcomes for review-agent happy-path case

• Adds annotations describing the expected PR state and labels after the review agent runs, plus scoring guidance for the LLM judge. Encodes turn and cost budgets for the case.

eval/review/cases/001-clean-approve/annotations.yaml

input.yamlAdd review-agent fixture PR for clean-approval scenario +85/-0

Add review-agent fixture PR for clean-approval scenario

• Defines a pull_request fixture that adds multiply/divide to a simple calc module with comprehensive tests, including division-by-zero behavior. Serves as the input scenario for the review agent functional eval.

eval/review/cases/001-clean-approve/input.yaml

README.mdAdd base repo README for review-agent fixture +18/-0

Add base repo README for review-agent fixture

• Provides minimal documentation for the fixture repository used as the PR base state in the review eval case.

eval/review/cases/001-clean-approve/repo/README.md

calc.pyAdd base calc module for review-agent fixture +11/-0

Add base calc module for review-agent fixture

• Creates the initial calc module containing add/subtract functions used as the pre-PR baseline for the review eval case.

eval/review/cases/001-clean-approve/repo/src/calc.py

test_calc.pyAdd base tests for calc module in review-agent fixture +19/-0

Add base tests for calc module in review-agent fixture

• Adds baseline unit tests for add/subtract in the fixture repository so the review case starts from a tested state.

eval/review/cases/001-clean-approve/repo/tests/test_calc.py

Other (2) +275 / -40
functional-tests.ymlRefactor functional tests into detect→matrix jobs with roll-up check +92/-40

Refactor functional tests into detect→matrix jobs with roll-up check

• Expands the workflow path filters and replaces the single functional-tests job gating logic with a 4-stage pipeline: gate, detect (compute agents JSON), functional-tests (parallel matrix per agent, fail-fast disabled), and functional-tests-complete (stable roll-up for branch protection). Artifacts are uploaded per agent, and secrets-gated steps are conditioned on availability.

.github/workflows/functional-tests.yml

eval.yamlAdd functional eval configuration for review agent +183/-0

Add functional eval configuration for review agent

• Introduces the review agent eval configuration, including case-mode execution, timeouts, environment wiring, fixture setup/teardown hooks, and judges/thresholds for review quality and label expectations.

eval/review/eval.yaml

@qodo-code-review

qodo-code-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (2)

Context used
✅ Compliance rules (platform): 55 rules
✅ Skills: 4 invoked
  code-review
  code-implementation
  pr-review
  docs-review

Grey Divider


Action required

1. FILE_COUNT unsanitized in ::warning 📜 Skill insight ⛨ Security
Description
The workflow emits a GitHub Actions workflow command with an interpolated variable ($FILE_COUNT)
that is not individually sanitized. This violates the requirement to sanitize every interpolated
value in workflow commands to prevent command injection via ::, encoded newlines, or control
characters.
Code

.github/workflows/functional-tests.yml[106]

+              echo "::warning::Compare API returned $FILE_COUNT files (possible truncation at 300) — running all functional tests as a precaution"
Relevance

⭐⭐⭐ High

Sanitizing interpolated values in workflow commands was explicitly accepted recently for ::error::
interpolation.

PR-#90

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538382 requires sanitizing every interpolated value in workflow commands. The line
emitting ::warning::... interpolates $FILE_COUNT directly with no sanitization.

.github/workflows/functional-tests.yml[104-107]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A GitHub Actions workflow command (`::warning::...`) includes an interpolated variable (`$FILE_COUNT`) that is not sanitized.

## Issue Context
Even if the current value is expected to be numeric, the compliance rule requires sanitizing each interpolated value in workflow commands to prevent workflow-command injection.

## Fix Focus Areas
- .github/workflows/functional-tests.yml[104-107]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Detect runs PR scripts 📜 Skill insight ⛨ Security
Description
On pull_request_target, the new detect job checks out the PR head and executes
.github/scripts/select-eval-agents.sh, meaning PR-controlled code runs in a context where repo
secrets are available. This exposes secrets to an untrusted context and violates the workflow
secrets isolation requirement.
Code

.github/workflows/functional-tests.yml[R121-145]

+      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+        with:
+          ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}
+          persist-credentials: false
+          allow-unsafe-pr-checkout: ${{ github.event_name == 'pull_request_target' }}
+
+      - name: Install yq
+        run: |
+          curl -sSfL "https://github.com/mikefarah/yq/releases/download/v4.47.1/yq_linux_amd64" -o /usr/local/bin/yq
+          chmod +x /usr/local/bin/yq
+
+      - name: Select eval agents
+        id: agents
+        env:
+          CHANGED_FILES: ${{ steps.changes.outputs.files }}
+        run: |
+          if [ -n "$CHANGED_FILES" ]; then
+            AGENTS=$(echo "$CHANGED_FILES" | .github/scripts/select-eval-agents.sh)
          else
-            echo "::notice::No functional-test-relevant files changed — skipping tests"
-            echo "relevant=false" >> "$GITHUB_OUTPUT"
+            # push/workflow_dispatch: run all agents with eval configs
+            AGENTS=$(for d in eval/*/eval.yaml; do basename "$(dirname "$d")"; done)
          fi
+          JSON=$(echo "$AGENTS" | jq -Rsc '[split("\n")[] | select(length > 0)]')
+          echo "agents=$JSON" >> "$GITHUB_OUTPUT"
+          echo "Selected agents: $JSON"
Relevance

⭐⭐ Medium

Repo already uses pull_request_target gating patterns; no clear prior acceptance/rejection on
running PR scripts post-checkout.

PR-#31
PR-#89

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538384 prohibits running fork/PR code with repo secrets under
pull_request_target. The workflow enables pull_request_target, then in detect checks out
github.event.pull_request.head.sha and runs .github/scripts/select-eval-agents.sh from that
checkout.

.github/workflows/functional-tests.yml[27-30]
.github/workflows/functional-tests.yml[121-139]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `detect` job runs on `pull_request_target`, checks out the PR head SHA, and executes a script from that checkout. This runs PR-controlled code in a secrets-capable context.

## Issue Context
`pull_request_target` has access to base-repo secrets; executing PR code in this context is prohibited.

## Fix Focus Areas
- .github/workflows/functional-tests.yml[27-30]
- .github/workflows/functional-tests.yml[121-145]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Roll-up misses gate/detect ✓ Resolved 🐞 Bug ≡ Correctness
Description
functional-tests-complete only fails when functional-tests reports failure, so it can still
succeed when detect is skipped/failed (including unauthorized PRs where detect is skipped) and no
tests ran, letting the required check pass incorrectly.
Code

.github/workflows/functional-tests.yml[R334-345]

+  functional-tests-complete:
+    needs: [detect, functional-tests]
+    if: always()
+    runs-on: ubuntu-24.04
+    timeout-minutes: 1
+    steps:
+      - name: Check results
+        run: |
+          if [ "${{ needs.functional-tests.result }}" = "failure" ]; then
+            echo "::error::One or more functional tests failed"
+            exit 1
+          fi
Relevance

⭐⭐ Medium

No historical evidence found on roll-up required-check jobs validating all needs results
(detect/gate) vs only matrix failures.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
detect is explicitly skipped unless the gate authorizes on pull_request_target, but the roll-up
job does not check needs.detect.result or gate authorization—only `needs.functional-tests.result
== failure`—so a skipped/failed detect path can still yield a successful roll-up required check.

.github/workflows/functional-tests.yml[76-81]
.github/workflows/functional-tests.yml[151-166]
.github/workflows/functional-tests.yml[334-345]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The roll-up job `functional-tests-complete` currently only checks `needs.functional-tests.result == failure`. This means it can report success when:
- `detect` is skipped (e.g., PR not authorized by gate)
- `detect` fails (so `functional-tests` is skipped)
In both cases, the stable required check can go green without any functional tests actually running.

## Issue Context
`detect` is gated by authorization for `pull_request_target`, and `functional-tests` is gated on `detect.outputs.agents != '[]'`.

## Fix Focus Areas
- .github/workflows/functional-tests.yml[76-81]
- .github/workflows/functional-tests.yml[151-166]
- .github/workflows/functional-tests.yml[334-345]

## Expected fix
Update `functional-tests-complete` to explicitly validate upstream job results:
- Fail if `needs.detect.result` is not `success` on events where functional tests are expected.
- Fail if `needs.functional-tests.result` is `failure`.
- Allow `needs.functional-tests.result == skipped` only when `needs.detect.outputs.agents == '[]'` (intentional no-op matrix).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Protected paths modified ✗ Dismissed 📜 Skill insight § Compliance
Description
This PR modifies protected governance/infrastructure paths under .github/ (workflows and scripts).
These changes require explicit human review and must not be auto-approved under the protected-path
governance policy.
Code

.github/workflows/functional-tests.yml[R9-26]

on:
  push:
    branches: [main]
-    # SYNC-WITH: grep regex in "Check for functional-test-relevant changes" step
    paths:
      - 'eval/**'
      - 'agents/**'
      - 'harness/**'
      - 'scripts/**'
      - 'schemas/**'
+      - 'env/**'
+      - 'common/**'
+      - 'policies/**'
+      - 'skills/**'
+      - 'plugins/**'
+      - 'docs/**'
      - '.github/workflows/functional-tests.yml'
      - '.github/scripts/**'
      - 'config.yaml'
Relevance

⭐⭐ Medium

Only related history is suggestions to document/justify protected-path changes;
enforcement/acceptance unclear.

PR-#29

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538392 requires raising a finding whenever protected paths are modified. The PR
changes .github/workflows/functional-tests.yml and adds new scripts under .github/scripts/.

.github/workflows/functional-tests.yml[1-26]
.github/scripts/select-eval-agents.sh[1-23]
.github/scripts/select-eval-agents-test.sh[1-10]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Protected governance/infrastructure paths were modified (not eligible for auto-approval).

## Issue Context
Changes under `.github/` require explicit human review per the protected-paths policy.

## Fix Focus Areas
- .github/workflows/functional-tests.yml[1-26]
- .github/scripts/select-eval-agents.sh[1-23]
- .github/scripts/select-eval-agents-test.sh[1-10]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Agent selection tests unused ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new .github/scripts/select-eval-agents-test.sh test suite is not invoked by the existing `make
script-test target used in CI, so regressions in select-eval-agents.sh` won’t be caught by the
script-test workflow.
Code

.github/scripts/select-eval-agents-test.sh[R1-6]

+#!/usr/bin/env bash
+# Tests for select-eval-agents.sh
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+SELECT_SCRIPT="${SCRIPT_DIR}/select-eval-agents.sh"
Relevance

⭐⭐⭐ High

Team previously wired new .github script tests into Makefile/CI (added check-e2e-authorization-test
to make script-test).

PR-#89

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added file is a standalone test runner, but the Makefile’s script-test target (the CI
entrypoint) enumerates specific scripts and doesn’t include it, and the script-test workflow runs
that Makefile target.

.github/scripts/select-eval-agents-test.sh[1-6]
Makefile[18-28]
.github/workflows/script-test.yml[32-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new unit test script was added, but it is not executed by the repo’s existing script-test CI entrypoint.

## Issue Context
CI runs `make script-test` (script-test workflow). The Makefile enumerates the scripts to run and currently omits the new test.

## Fix Focus Areas
- Makefile[18-28]
- .github/workflows/script-test.yml[32-35]
- .github/scripts/select-eval-agents-test.sh[1-6]

## Expected fix
Add a line to `Makefile` `script-test:` target to run:
- `bash .github/scripts/select-eval-agents-test.sh`
so the new tests run in CI alongside the existing script tests.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Unverified yq download ✓ Resolved 🐞 Bug ⛨ Security
Description
The workflow downloads a prebuilt yq binary and executes it without a pinned SHA-256 verification
in both detect and functional-tests, which increases CI supply-chain risk compared to the repo’s
existing verified install pattern.
Code

.github/workflows/functional-tests.yml[R127-131]

+      - name: Install yq
+        run: |
+          curl -sSfL "https://github.com/mikefarah/yq/releases/download/v4.47.1/yq_linux_amd64" -o /usr/local/bin/yq
+          chmod +x /usr/local/bin/yq
+
Relevance

⭐⭐⭐ High

Hardening yq download with pinned SHA-256 verification was previously accepted in CI workflows.

PR-#80

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
functional-tests.yml installs yq twice using only curl+chmod, while script-test.yml installs the
same yq version but verifies a pinned SHA-256; a past accepted bug explicitly called out the need to
harden yq install integrity checks.

.github/workflows/functional-tests.yml[127-131]
.github/workflows/functional-tests.yml[184-188]
.github/workflows/script-test.yml[26-31]
PR-#80

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow installs `yq` by downloading an executable from GitHub Releases and making it executable without verifying integrity.

## Issue Context
The repo already demonstrates a checksum verification pattern in `.github/workflows/script-test.yml`.

## Fix Focus Areas
- .github/workflows/functional-tests.yml[127-131]
- .github/workflows/functional-tests.yml[184-188]
- .github/workflows/script-test.yml[26-31]

## Expected fix
For both yq install steps in functional-tests.yml:
- Add the pinned SHA-256 verification (same style as script-test.yml), e.g. `echo "<sha>  /usr/local/bin/yq" | sha256sum -c` before `chmod +x`.
- Optionally download to a temp path then move into place after verification.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Assumes script executable ✓ Resolved 🐞 Bug ☼ Reliability
Description
detect runs .github/scripts/select-eval-agents.sh directly (not via bash), which makes the
workflow dependent on the file’s executable bit; the repo otherwise invokes .github/scripts/*.sh
via bash, so this is an inconsistent and fragile execution path.
Code

.github/workflows/functional-tests.yml[R132-144]

+      - name: Select eval agents
+        id: agents
+        env:
+          CHANGED_FILES: ${{ steps.changes.outputs.files }}
+        run: |
+          if [ -n "$CHANGED_FILES" ]; then
+            AGENTS=$(echo "$CHANGED_FILES" | .github/scripts/select-eval-agents.sh)
          else
-            echo "::notice::No functional-test-relevant files changed — skipping tests"
-            echo "relevant=false" >> "$GITHUB_OUTPUT"
+            # push/workflow_dispatch: run all agents with eval configs
+            AGENTS=$(for d in eval/*/eval.yaml; do basename "$(dirname "$d")"; done)
          fi
+          JSON=$(echo "$AGENTS" | jq -Rsc '[split("\n")[] | select(length > 0)]')
+          echo "agents=$JSON" >> "$GITHUB_OUTPUT"
Relevance

⭐⭐ Medium

No historical evidence on requiring bash invocation vs executable bit for .github/scripts in
workflows.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The detect job currently invokes the new script directly, while the composite action and Makefile
explicitly use bash to run .github/scripts/*.sh, indicating the workflow is relying on
executable permissions in a way the repo generally avoids.

.github/workflows/functional-tests.yml[132-145]
.github/actions/check-e2e-authorization/action.yml[44-56]
Makefile[18-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow executes `.github/scripts/select-eval-agents.sh` directly in a pipeline. If the file isn’t committed with executable mode (or if a checkout preserves it unexpectedly), `detect` will fail.

## Issue Context
Other repo scripts under `.github/scripts/` are invoked via `bash` (composite action + Makefile), suggesting the intended convention is `bash <script>`.

## Fix Focus Areas
- .github/workflows/functional-tests.yml[132-144]
- .github/actions/check-e2e-authorization/action.yml[44-56]
- Makefile[18-28]

## Expected fix
Change the invocation to:
- `AGENTS=$(echo "$CHANGED_FILES" | bash .github/scripts/select-eval-agents.sh)`
(or `AGENTS=$(echo "$CHANGED_FILES" | .github/scripts/select-eval-agents.sh)` plus ensure the script is committed with mode 100755).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread .github/workflows/functional-tests.yml
Comment thread .github/workflows/functional-tests.yml
Comment thread .github/workflows/functional-tests.yml
Comment thread .github/workflows/functional-tests.yml
Comment thread .github/workflows/functional-tests.yml
Comment thread .github/scripts/select-eval-agents-test.sh
Comment thread .github/workflows/functional-tests.yml
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:38 AM UTC · Completed 11:56 AM UTC
Commit: 961cf67 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review

Re-review of 99c0f1a (prior review at 265a4b3, provenance: app-verified).

This PR restructures the functional-tests workflow from a single sequential job into a 4-job pipeline (gate → detect → matrix functional-tests → roll-up) and adds a review agent eval suite. Two commits since the prior review address most prior findings: gate added to roll-up needs with GATE_RESULT checking, expected_labels renamed to required_labels, tests added for empty stdin/duplicate inputs/all-variable harness, and { grep -v '\$' || true; } fix for variable-only harness files.

Prior findings addressed

Finding Prior severity Status
[command-injection]${{ matrix.agent }} in run: block High Fixed — passed via env: AGENT
[heredoc-delimiter-injection] — static GHEOF delimiter Low Fixed — randomized via openssl rand -hex 8
[error-handling-gap] — roll-up didn't check gate Medium Partially addressed — see below
[untrusted-code-execution] — detect runs untrusted script Low ⬇ Maintained at low — gate mitigates
[naming-convention]expected_labels vs required_labels Low Fixed — renamed to required_labels
[test-adequacy] — missing empty stdin / duplicate tests Low Fixed — tests added
[logic-error]grep -v exit 1 on all-variable harness Low Fixed — `{ ...
yq binary integrity N/A Improved — SHA256 verification added

Findings

Medium

  • [error-handling-gap] .github/workflows/functional-tests.yml — The functional-tests-complete roll-up now has needs: [gate, detect, functional-tests] and checks GATE_RESULT for failure/cancelled — a meaningful improvement over the prior state. However, the gap for the unauthorized-fork-PR scenario persists: when gate succeeds but outputs authorized=false, detect and functional-tests are skipped (their if conditions check authorized == 'true'). The roll-up only fails on failure/cancelled, not skipped, so it passes green. If functional-tests-complete is the sole required status check, unauthorized fork PRs get a green check. This has been flagged across four consecutive reviews; the latest fix narrows the gap but doesn't close it.
    Remediation: Add a pull_request_target-specific check in the roll-up: pass needs.gate.outputs.authorized as an env var and fail when it's not true on pull_request_target events, or fail when TESTS_RESULT == 'skipped' and the event is pull_request_target. Alternatively, document that gate must be configured as a separate required status check alongside functional-tests-complete.

Low

  • [untrusted-code-execution] .github/workflows/functional-tests.yml — The detect job checks out untrusted PR head code and runs select-eval-agents.sh from it. Mitigated by gate restricting to MEMBER/COLLABORATOR, and functional-tests independently checks out the same PR head. Maintained at low from prior review.
    Remediation: For defense-in-depth, run the script from the base-ref checkout or fetch via the GitHub API.

  • [logic-error] .github/workflows/functional-tests.yml — In the detect job's workflow_dispatch fallback, for d in eval/*/eval.yaml runs without shopt -s nullglob. If no eval configs exist, the glob expands literally and basename produces *, creating a matrix with ["*"]. Unlikely but would produce a confusing downstream failure.
    Remediation: Add shopt -s nullglob before the glob, or validate that AGENTS is non-empty after the loop.

  • [missing-workflow-documentation] README.md — The Workflows table does not include functional-tests.yml. Pre-existing gap (not introduced by this PR), but this PR significantly restructures the workflow. The README also references "seven" test suites when the actual count is higher.

Summary

Substantial progress since the prior review: 6 of 8 prior findings are fully resolved, and the remaining medium finding (roll-up gate gap) has been partially addressed by adding gate to the needs list and checking GATE_RESULT. The security posture has improved significantly — all high-severity findings from the original review are fixed, yq integrity verification was added, and the grep -v edge case is handled. The remaining medium finding has a straightforward configuration workaround (configure gate as a separate required status check) and no unauthorized code can execute regardless of the roll-up check state. The architecture is sound and well-tested (20 test scenarios for the new script).

Previous run

Review

Re-review of 265a4b3 (prior review at 6f11dd3, provenance: app-verified).

Findings

Medium

  • [error-handling-gap] .github/workflows/functional-tests.yml — The functional-tests-complete roll-up job has needs: [detect, functional-tests] without gate. On a pull_request_target where gate denies authorization (authorized != true), both detect and functional-tests are skipped. The roll-up runs (if: always()) and its check step only fails on failure or cancelledskipped silently passes.

Low

  • [untrusted-code-execution] .github/workflows/functional-tests.yml — detect runs untrusted PR head script. Downgraded from medium: gate mitigates.
  • [test-adequacy] .github/scripts/select-eval-agents-test.sh — Missing test cases for empty stdin and duplicate file inputs.
  • [naming-convention] eval/review/eval.yaml — Judge name expected_labels inconsistent with triage eval's required_labels.
  • [missing-workflow-documentation] README.md — Workflows table does not include functional-tests.yml.
  • [logic-error] .github/workflows/functional-tests.ymlfor d in eval/*/eval.yaml without shopt -s nullglob.
First run

Review

Re-review of 6f11dd3 (prior review at 961cf67, provenance: app-verified).

Findings

Medium

  • [error-handling-gap] .github/workflows/functional-tests.yml:340 — The functional-tests-complete roll-up job has needs: [detect, functional-tests] without gate. On a pull_request_target where gate denies authorization, both detect and functional-tests are skipped. The roll-up runs and silently passes.

Low

  • [untrusted-code-execution] .github/workflows/functional-tests.yml:152 — detect runs untrusted PR head script. Downgraded from medium: gate mitigates.
  • [test-adequacy] .github/scripts/select-eval-agents-test.sh — Missing test cases for empty stdin and duplicate file inputs.
  • [naming-convention] eval/review/eval.yaml:109 — Judge name expected_labels inconsistent with triage eval's required_labels.
  • [missing-workflow-documentation] README.md:57 — Workflows table does not include functional-tests.yml.
  • [scope-creep] eval/review/ — Review agent eval suite bundled with CI restructuring.
First run

High

  • [error-handling-gap] .github/workflows/functional-tests.yml:340 — Roll-up job only checks needs.functional-tests.result but not needs.detect.result.
  • [command-injection] .github/workflows/functional-tests.yml:313${{ matrix.agent }} interpolated directly into run: command with id-token: write and secret access.
  • [protected-path] .github/workflows/functional-tests.yml — Modifies 3 files under .github/ without a linked issue.

Medium

  • [untrusted-code-execution] .github/workflows/functional-tests.yml:130 — detect checks out untrusted PR head code and runs script from it.
  • [missing-workflow-documentation] README.md:57 — Workflows table does not include functional-tests.yml.
Previous run

Review

Re-review of 265a4b3 (prior review at 6f11dd3, provenance: app-verified).

This PR restructures the functional-tests workflow from a single sequential job into a 4-job pipeline (gate → detect → matrix functional-tests → roll-up) and adds a review agent eval suite. The architecture is sound — matrix-based parallelism with a stable roll-up job name for branch protection is the standard GitHub Actions pattern for dynamic test matrices.

Prior findings addressed

Finding Prior severity Status
[command-injection]${{ matrix.agent }} in run: block High Fixed — now passed via env: AGENT
[heredoc-delimiter-injection] — static GHEOF delimiter Low Fixed — randomized via openssl rand -hex 8
[error-handling-gap] — roll-up didn't check detect.result High → Medium Partially addressed — detect checked, gate still missing from needs
[untrusted-code-execution] — detect runs untrusted script Medium → Low ⬇ Maintained at low — gate authorization mitigates
yq binary integrity N/A Improved — SHA256 verification added

Findings

Medium

  • [error-handling-gap] .github/workflows/functional-tests.yml — The functional-tests-complete roll-up job has needs: [detect, functional-tests] without gate. On a pull_request_target where gate denies authorization (authorized != true), both detect and functional-tests are skipped. The roll-up runs (if: always()) and its check step only fails on failure or cancelledskipped silently passes. If functional-tests-complete is the sole required status check for branch protection, an unauthorized fork PR would see a green required check. This was flagged in both prior reviews and remains unaddressed.
    Remediation: Either (1) add gate to the roll-up's needs list and check for needs.gate.result, explicitly failing when gate is skipped or fails on pull_request_target; or (2) check for skipped state — fail when needs.functional-tests.result == 'skipped' on pull_request_target events; or (3) document that gate must be configured as a separate required status check alongside functional-tests-complete in branch protection settings.

Low

  • [untrusted-code-execution] .github/workflows/functional-tests.yml — The detect job checks out untrusted PR head code and runs select-eval-agents.sh from it. Its output feeds the privileged functional-tests matrix. Mitigated by gate restricting to MEMBER/COLLABORATOR, and the functional-tests job independently checking out the same PR head (so detect adds no new code-execution surface). Maintained at low from prior review.
    Remediation: For defense-in-depth, consider running the script from the base-ref checkout or fetching it via the GitHub API.

  • [test-adequacy] .github/scripts/select-eval-agents-test.sh — Missing test cases for empty stdin (no changed files) and duplicate file inputs. The script handles empty stdin correctly (exit 0), but explicit test coverage would prevent regressions. Carried forward from prior review.

  • [naming-convention] eval/review/eval.yaml:109 — Judge name expected_labels is inconsistent with triage eval's required_labels (at eval/triage/eval.yaml:109) for functionally identical checks. Both check for required labels from annotations.yaml.
    Remediation: Rename to required_labels for cross-agent consistency.

  • [missing-workflow-documentation] README.md:57 — The Workflows table does not include functional-tests.yml. Pre-existing gap, not introduced by this PR, but this PR significantly restructures the workflow. The README also references "seven" test suites (line 51) when the Makefile currently runs 10 (11 with this PR's addition).

  • [logic-error] .github/workflows/functional-tests.yml — In the detect job's workflow_dispatch fallback, the glob for d in eval/*/eval.yaml runs without shopt -s nullglob. If no eval configs exist (unlikely but possible on a corrupted checkout), the glob expands to the literal string and basename produces *, creating a matrix with ["*"] that fails downstream with a confusing error.
    Remediation: Add shopt -s nullglob before the glob, or validate that AGENTS is non-empty after the loop.

Summary

The high-severity security findings from the first review (command injection, HEREDOC delimiter injection) are both fixed, and yq integrity verification was added. The remaining medium finding (roll-up gate gap) has been flagged in three consecutive reviews — it is worth resolving, but has a straightforward workaround via branch protection configuration. The PR is well-tested (18 test scenarios for the new script), the architecture is sound, and the overall security posture has improved significantly.

Previous run (2)

Review

Re-review of 6f11dd3 (prior review at 961cf67, provenance: app-verified).

Findings

Medium

  • [error-handling-gap] .github/workflows/functional-tests.yml:340 — The functional-tests-complete roll-up job has needs: [detect, functional-tests] without gate. On a pull_request_target where gate denies authorization, both detect and functional-tests are skipped. The roll-up runs and silently passes.

Low

  • [untrusted-code-execution] .github/workflows/functional-tests.yml:152 — detect runs untrusted PR head script. Downgraded from medium: gate mitigates.
  • [test-adequacy] .github/scripts/select-eval-agents-test.sh — Missing test cases for empty stdin and duplicate file inputs.
  • [naming-convention] eval/review/eval.yaml:109 — Judge name expected_labels inconsistent with triage eval's required_labels.
  • [missing-workflow-documentation] README.md:57 — Workflows table does not include functional-tests.yml.
  • [scope-creep] eval/review/ — Review agent eval suite bundled with CI restructuring.
First run

High

  • [error-handling-gap] .github/workflows/functional-tests.yml:340 — Roll-up job only checks needs.functional-tests.result but not needs.detect.result.
  • [command-injection] .github/workflows/functional-tests.yml:313${{ matrix.agent }} interpolated directly into run: command with id-token: write and secret access.
  • [protected-path] .github/workflows/functional-tests.yml — Modifies 3 files under .github/ without a linked issue.

Medium

  • [untrusted-code-execution] .github/workflows/functional-tests.yml:130 — detect checks out untrusted PR head code and runs script from it.
  • [missing-workflow-documentation] README.md:57 — Workflows table does not include functional-tests.yml.
Previous run (3)

Review

Re-review of 6f11dd3 (prior review at 961cf67, provenance: app-verified).

This PR restructures the functional-tests workflow from a single sequential job into a 4-job pipeline (gate → detect → matrix functional-tests → roll-up) and adds a review agent eval suite. The restructuring is well-designed — matrix-based parallelism with a stable roll-up job name for branch protection is the standard GitHub Actions pattern for dynamic test matrices.

Prior findings addressed

Finding Prior severity Status
[command-injection]${{ matrix.agent }} in run: block High Fixed — now passed via env: AGENT
[error-handling-gap] — roll-up didn't check detect.result High Partially fixed — detect checked, but gate still missing from needs
[heredoc-delimiter-injection] — static GHEOF delimiter Low Fixed — randomized via openssl rand -hex 8
[untrusted-code-execution] — detect runs untrusted script Medium Downgraded to low — gate authorization mitigates
yq binary integrity N/A Improved — SHA256 verification added

Findings

Medium

  • [error-handling-gap] .github/workflows/functional-tests.yml:340 — The functional-tests-complete roll-up job has needs: [detect, functional-tests] without gate. On a pull_request_target where gate denies authorization (authorized=false), both detect and functional-tests are skipped. The roll-up's check step only fails on failure or cancelledskipped silently passes. Since the PR body and comments describe functional-tests-complete as the stable required check name for branch protection, an unauthorized fork PR could see a green required check if this is the sole required status.
    Remediation: Add gate to the roll-up's needs list and check for gate.result == 'failure' || gate.result == 'cancelled', or explicitly fail when detect is skipped on a pull_request_target event. Alternatively, document that gate must also be configured as a separate required status check.

Low

  • [untrusted-code-execution] .github/workflows/functional-tests.yml:152 — The detect job checks out untrusted PR head code and runs select-eval-agents.sh from it. Its output feeds the privileged functional-tests matrix. An attacker who passes gate authorization could modify the script to manipulate the output. Downgraded from medium: the gate already restricts to MEMBER/COLLABORATOR, and the functional-tests job independently checks out the same PR head, so detect adds no new attack surface.
    Remediation: For defense-in-depth, consider running the script from a trusted base-ref checkout or fetching it via the GitHub API.

  • [test-adequacy] .github/scripts/select-eval-agents-test.sh — Missing test cases for empty stdin (no changed files) and duplicate file inputs. The script handles empty stdin correctly (exit 0), but explicit test coverage would prevent regressions.

  • [naming-convention] eval/review/eval.yaml:109 — Judge name expected_labels is inconsistent with triage eval's required_labels for functionally identical checks.
    Remediation: Rename to required_labels for cross-agent consistency.

  • [missing-workflow-documentation] README.md:57 — The Workflows table does not include functional-tests.yml. Pre-existing gap, not introduced by this PR, but this is a natural opportunity to add it.

  • [scope-creep] eval/review/ — The review agent eval suite is bundled with the CI restructuring. The coupling is justified (validating matrix strategy with multiple agents), but the PR description could note this explicitly.

Summary

Significant security improvements since the prior review — the high-severity command injection and HEREDOC delimiter issues are both fixed, and yq integrity verification was added. The remaining medium finding (roll-up gate gap) is worth addressing but is not blocking given that branch protection can be configured with gate as a separate required check. The PR is well-tested (16 test scenarios for the new script) and the architecture is sound.

Previous run (4)

Review

Findings

High

  • [error-handling-gap] .github/workflows/functional-tests.yml:340 — The functional-tests-complete roll-up job only checks needs.functional-tests.result but does not check needs.detect.result. If detect fails (e.g., yq install failure, script bug, checkout failure), functional-tests is skipped (result='skipped'), and the roll-up passes silently — allowing merges when CI infrastructure is broken.
    Remediation: Add a check for needs.detect.result in the Check results step.

  • [command-injection] .github/workflows/functional-tests.yml:313${{ matrix.agent }} is interpolated directly into a run: command (run: ./eval/run-functional.sh ${{ matrix.agent }}). The values originate from the detect job which runs attacker-controlled code from the PR checkout. Since functional-tests has id-token: write and access to secrets (EVAL_GH_TOKEN, GCP credentials), this is an expression injection vector.
    Remediation: Use an environment variable: env: AGENT: ${{ matrix.agent }} with run: ./eval/run-functional.sh "$AGENT". Validate values against ^[a-z0-9-]+$.

  • [protected-path] .github/workflows/functional-tests.yml — This PR modifies 3 files under .github/ (protected path) without a linked issue: select-eval-agents-test.sh, select-eval-agents.sh, functional-tests.yml. Human approval is required for protected-path changes.
    Remediation: Link to an authorizing issue.

Medium

  • [untrusted-code-execution] .github/workflows/functional-tests.yml:130 — The detect job checks out untrusted PR head code and executes .github/scripts/select-eval-agents.sh from it in a pull_request_target context. While limited to contents: read, its output feeds the privileged functional-tests matrix.
    Remediation: Run select-eval-agents.sh from the base branch, or inline the agent selection logic.

  • [missing-workflow-documentation] README.md:57 — The Workflows table does not include functional-tests.yml. Pre-existing gap, but this PR significantly refactors the workflow.

Low

  • [logic-error-reduced-coverage] .github/workflows/functional-tests.yml:334 — Roll-up if: always() converts all skip scenarios (including gate denial) into a green check. See also: [error-handling-gap].
  • [test-adequacy] .github/scripts/select-eval-agents-test.sh:3set -euo pipefail aborts on first unexpected failure instead of recording and continuing.
  • [expression-injection-artifact-name] .github/workflows/functional-tests.yml:323eval-results-${{ matrix.agent }} — same root cause as [command-injection].
  • [heredoc-delimiter-injection] .github/workflows/functional-tests.yml:127GHEOF delimiter theoretically injectable via crafted filenames. Practically low risk.
  • [missing-authorization] No linked issue for CI restructuring. PR body provides detailed rationale and references ci: dynamically select eval agents from changed files #145.
  • [shell-script-header-format] .github/scripts/select-eval-agents.sh:2, .github/scripts/select-eval-agents-test.sh:2 — Minor header style differences from established patterns.
  • [workflow-job-description-format] .github/workflows/functional-tests.yml:88 — Separator comment blocks atypical of workflows in this repo.
  • [error-message-format] .github/workflows/functional-tests.yml:108 — Warning messages "running all functional tests" follow an indirect code path (API failure → no output → fallback to all agents).
  • [inline-script-organization] .github/workflows/functional-tests.yml:140 — Orchestration logic in workflow step could be simpler.
  • [shell-array-usage] .github/scripts/select-eval-agents.sh:24mapfile requires bash 4+; consistent with existing repo patterns.

fullsend-ai-review[bot]

This comment was marked as outdated.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review squad pass (4 agents: Claude, Claude, Gemini, Codex) — new findings not already covered by the prior qodo-code-review pass. Full triage report was shared inline in the session; posting the unique Medium+ findings here.

Note: 3 of qodo-code-review's existing findings (roll-up masking gate/detect failures, unverified yq download, unwired test suite) are still open/unreplied and match findings independently raised by this squad — not reposted here to avoid duplication, but worth resolving alongside these.

Comment thread .github/workflows/functional-tests.yml Outdated
Comment thread .github/scripts/select-eval-agents.sh Outdated
Comment thread .github/workflows/functional-tests.yml
Comment thread .github/workflows/functional-tests.yml
Comment thread .github/workflows/functional-tests.yml
Comment thread .github/scripts/select-eval-agents.sh Outdated
Comment thread .github/scripts/select-eval-agents-test.sh
@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

🤖 Finished Fix · ❌ Failure · Started 11:57 AM UTC · Completed 12:17 PM UTC
Commit: 961cf67 · View workflow run →

@ralphbean
ralphbean force-pushed the ci/matrix-eval-strategy branch from 961cf67 to 6f11dd3 Compare July 13, 2026 14:07
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:08 PM UTC · Completed 2:21 PM UTC
Commit: 6f11dd3 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review July 13, 2026 14:21

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 13, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:22 PM UTC · Completed 4:33 PM UTC
Commit: 265a4b3 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 13, 2026
ralphbean added a commit that referenced this pull request Jul 13, 2026
- Add gate to roll-up job needs list so unauthorized PRs don't get a
  green functional-tests-complete check
- Rename expected_labels to required_labels in review eval for
  consistency with triage eval
- Add tests for empty stdin and duplicate file inputs

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:04 PM UTC · Ended 5:16 PM UTC
Commit: bd28f41 · View workflow run →

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-review squad pass (3 agents: Claude, Claude, Gemini) against the current head (265a4b3), after the 3 follow-up fix commits. No CRITICAL or HIGH issues found — verified the matrix.agent injection fix, GITHUB_OUTPUT delimiter fix, yq checksum pinning, push-event diff-based selection, and test-suite CI wiring are all correctly implemented.

One new MEDIUM regression from the fix itself, posted inline (extract_refs pipefail/grep-empty false-failure — currently dormant, no active harness file triggers it).

Worth noting for follow-up (not blocking this approval):

  • The roll-up job (functional-tests-complete) now includes gate in needs and checks GATE_RESULT, but this doesn't fully close the previously-flagged gap (fullsend-ai-review comment on line 352/373): check-e2e-authorization never fails the gate job on a denied/unauthorized PR (it only sets authorized=false and posts a comment), so GATE_RESULT stays success in that case and the roll-up still can't distinguish "denied authorization, nothing ran" from "legitimately nothing to test." Still open, unreplied.
  • A couple of LOW items from earlier bot passes remain open and unreplied (judge-naming inconsistency in eval/review/eval.yaml, missing empty-stdin/duplicate-input test cases) — not re-flagged here since already tracked.

Approving based on no blocking issues in the current diff.

Comment thread .github/scripts/select-eval-agents.sh Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:17 PM UTC · Completed 5:27 PM UTC
Commit: 99c0f1a · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 13, 2026
@ralphbean
ralphbean added this pull request to the merge queue Jul 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 13, 2026
ralphbean and others added 8 commits July 13, 2026 15:19
The workflow was hardcoded to only run triage evals. Add a step
to also run the review eval cases.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Replace hardcoded agent names in the functional-tests workflow with a
script that parses harness/*.yaml to determine which agents' tests to
run based on the changed files in a PR. This allows new agents to be
added (with their eval configs) in a single PR without also needing to
modify the workflow file — solving the pull_request_target chicken-and-egg
problem.

The select-eval-agents.sh script reads changed files on stdin, extracts
all file path references from each harness YAML (agent prompt, doc,
policy, scripts, host_files, skills, plugins, schemas, forge scripts),
and outputs agent names whose referenced files were touched. Only agents
with an eval/<agent>/eval.yaml config are candidates.

Includes 15 test cases covering direct harness changes, transitive
references (env files, shared scripts, skill/plugin subdirectories),
multi-agent selection, exclusion of agents without eval configs, and
variable host_file path filtering.

Signed-off-by: Ryan Beans <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Restructure the functional-tests workflow into four jobs:

- gate: PR authorization (unchanged)
- detect: lightweight job that checks out the PR, runs
  select-eval-agents.sh, and outputs a JSON array of agent names
- functional-tests: matrix over detected agents, each leg does full
  setup and runs run-functional.sh independently and in parallel
- functional-tests-complete: roll-up job with a stable name for branch
  protection required checks

The matrix approach gives true parallelism (triage and review run
simultaneously on separate runners), independent failure isolation
(fail-fast: false), and clean per-agent log streams. The roll-up job
handles all dynamic matrix shapes including empty (no agents to test).

Artifact names are per-agent (eval-results-<agent>) to avoid conflicts
between matrix legs.

Signed-off-by: Ryan Beans <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…d FIXTURE_NUMBER

The functional-tests-complete roll-up job only checked for "failure"
from matrix legs. A cancelled leg (e.g., runner preemption) would
silently pass the roll-up, giving a false green on the required check.
Add a cancelled check alongside failure.

Also add a :? guard on FIXTURE_NUMBER in run-fullsend.sh for
consistency with the other fixture variables — gives a clear error
message if the before_each hook fails to set it.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…ilures

Address review feedback:

- Pass matrix.agent through env var to prevent expression injection in
  the run: block (the critical security finding)
- Validate agent names against ^[a-zA-Z0-9_-]+$ in select-eval-agents.sh
  as defense-in-depth against malicious harness filenames
- Remove 2>/dev/null from yq in extract_refs so parse failures surface
  instead of silently producing empty ref lists
- Replace process substitution with command substitution for extract_refs
  so set -e can catch failures
- Add sha256 verification for yq downloads (matching script-test.yml)
- Use random heredoc delimiter instead of static GHEOF
- Add truncation detection for PR file lists (matching merge_group path)
- Check detect job result in the roll-up job so a broken detect cannot
  silently pass the required check
- Invoke select-eval-agents.sh via bash for consistency
- Add select-eval-agents-test.sh to Makefile script-test target
- Add tests for malformed YAML and invalid agent names

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…filter embedded vars

- Add push event diff-based selection using github.event.before/after
  compare API, same pattern as merge_group — avoids running all agents
  unconditionally on every push to main
- Filter variable references containing $ anywhere in the value, not
  just at the start — prevents mid-path vars like env/${AGENT}.env from
  passing through as unresolvable literals
- Add test for embedded variable filtering

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
- Add gate to roll-up job needs list so unauthorized PRs don't get a
  green functional-tests-complete check
- Rename expected_labels to required_labels in review eval for
  consistency with triage eval
- Add tests for empty stdin and duplicate file inputs

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
When every tracked field in a harness YAML is a variable reference,
grep -v filters out all lines and exits 1. Under pipefail, this made
extract_refs return non-zero, which the caller misinterpreted as a yq
parse failure. Wrap grep in { ... || true; } so an empty match set is
treated as legitimate empty output rather than a fatal error.

Add a test covering this scenario.

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean
ralphbean force-pushed the ci/matrix-eval-strategy branch from 99c0f1a to feaaed4 Compare July 13, 2026 19:20
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:21 PM UTC · Completed 7:38 PM UTC
Commit: feaaed4 · View workflow run →

@ralphbean
ralphbean added this pull request to the merge queue Jul 13, 2026
Merged via the queue into main with commit 51561b2 Jul 13, 2026
8 checks passed
@ralphbean
ralphbean deleted the ci/matrix-eval-strategy branch July 13, 2026 19:27
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jul 13, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 7:29 PM UTC · Completed 7:39 PM UTC
Commit: feaaed4 · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review skipped — this PR is already merged.

The /fs-review command only reviews open pull requests.

Posted by fullsend post-review check

@fullsend-ai-retro

Copy link
Copy Markdown

PR #148 was a human-authored CI restructuring (parallel functional tests with dynamic matrix strategy) that went through 4 successful review rounds, 1 failed fix agent run, and 5 human fix commits before merging ~8 hours after creation. Total agent cost was ~$23.62 ($18.78 review + $4.84 fix). Review quality was strong: the review agent caught critical security issues (command injection, heredoc injection, error handling gaps) on the first pass and tracked findings across re-reviews with a clear status table. The fix agent produced correct fixes but its push was rejected because the GitHub App token lacks workflows permission — a known issue (#139). The human then manually implemented the same fixes, wasting $4.84 plus human time. A merge queue ejection occurred due to an unrelated eval failure: post-review.sh got a 422 from the GitHub API when submitting a formal review to a fixture repo. This PR also resolved #93 (yq sha256 verification) which remains open.

Proposals

  1. Pre-fix script should skip fix agent when review findings only affect workflow files — prevents $4.84+ of wasted compute when the push will be rejected
  2. Post-review.sh should handle GitHub API 422 errors — prevents false eval failures and production review submission failures
  3. Close issue Backport sha256 verification for yq download in functional-tests.yml #93 — resolved by this PR's sha256 verification addition

Proposals filed

rh-hemartin pushed a commit that referenced this pull request Jul 16, 2026
The 001-clean-approve review eval case has been broken
since PR #148 merged. The post-review script returns 422
errors from inline review comments citing invalid line
numbers, which prevents the ready-for-merge label from
being applied. This causes the required_labels judge to
fail (pass_rate=0.0%), blocking functional-tests-complete
and the entire merge queue.

Remove the case directory to unblock the merge queue. The
review eval infrastructure (eval.yaml, hooks, judges,
thresholds) is preserved. The test case should be
reintroduced after the line number accuracy issue is
fixed (see PR #197).

Closes #202
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fullsend-fix requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants