Skip to content

Add CI Shepherd to main - #867

Open
oxve wants to merge 1 commit into
mainfrom
merge-shepherd-to-main-5dcc52a8
Open

oxve wants to merge 1 commit into
mainfrom
merge-shepherd-to-main-5dcc52a8

Conversation

@oxve

@oxve oxve commented Aug 17, 2026

Copy link
Copy Markdown
Member

Merge ci_shepherd.yml and script files to main branch for collation testing.

These are needed on main branch to trigger on workflow_run events.

Tag: agy
Conv: 5dcc52a8-9b21-44ce-bdad-7d0105d07dc9
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Suggested Commit Message


ci: Implement CI Shepherd for report collation

Introduce the CI Shepherd workflow and supporting scripts to aggregate
results from disparate CI runs. This workflow triggers upon the
completion of platform-specific builds (Android, AOSP, Evergreen, and
Linux) to collect validation report fragments.

The collected data is collated into a single summary, formatted as a
Markdown table, and posted as a status update and pull request
comment. This provides a unified view of CI health across all
supported platforms, simplifying the verification process for
developers.

Issue: 867

💡 Pro Tips for a Better Commit Message:

  1. Influence the Result: Want to change the output? You can write custom prompts or instructions directly in the Pull Request description. The model uses that text to generate the message.
  2. Re-run the Generator: Post a comment with: /generate-commit-message

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces two new Python scripts, collate_ci_reports.py and format_ci_report.py, to aggregate and format GitHub Actions CI workflow reports into a unified Markdown table. The review feedback highlights several robust improvements: handling potential command failures when downloading artifacts to prevent script crashes, clearing the temporary fragments directory to avoid stale data, defensively accessing dictionary keys to prevent KeyError exceptions, and providing fallback defaults for missing report fields to ensure clean Markdown output.

Comment on lines +109 to +118
run_cmd([
'gh',
'run',
'download',
run_id,
'--pattern',
'validation-report-*',
'--dir',
'fragments',
])

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.

high

If a workflow run fails before generating any artifacts (e.g., due to a compilation or setup failure), gh run download will fail with a non-zero exit code. Since run_cmd uses check=True, this will raise a CalledProcessError and crash the entire collation script, preventing other workflows from being reported. Wrapping this in a try-except block allows the script to handle failures gracefully and record the error.

    try:
      run_cmd([
          'gh',
          'run',
          'download',
          run_id,
          '--pattern',
          'validation-report-*',
          '--dir',
          'fragments',
      ])
    except subprocess.CalledProcessError:
      if conclusion != 'success':
        report_data.append({
            'workflow': wf_name,
            'status': 'completed',
            'error': f'Workflow failed ({conclusion})'
        })
      else:
        report_data.append({
            'workflow': wf_name,
            'status': 'completed',
            'error': 'Failed to download validation report'
        })
ci_runs.append(r)
seen_workflows.add(wf_name)

os.makedirs('fragments', exist_ok=True)

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.

medium

If this script is run multiple times in a persistent environment or locally, stale JSON files in the fragments directory from previous runs will be read and merged into the new report. It is safer to clear the directory before downloading new fragments.

Suggested change
os.makedirs('fragments', exist_ok=True)
import shutil
shutil.rmtree('fragments', ignore_errors=True)
os.makedirs('fragments', exist_ok=True)
f" ({entry['error']}) |")
else:
d = entry['data']
status_icon = '❌ FAIL' if d['failed'] else '✅ PASS'

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.

medium

Accessing d['failed'] directly can raise a KeyError if the key is missing from the JSON fragment. Using .get('failed') is safer and aligns with defensive programming practices.

Suggested change
status_icon = '❌ FAIL' if d['failed'] else '✅ PASS'
status_icon = '❌ FAIL' if d.get('failed') else '✅ PASS'
Comment on lines +59 to +63
markdown_lines.append(
f"| {d.get('platform')} | {d.get('build_result')} |"
f" {d.get('on_host_test_result')} |"
f" {d.get('on_device_test_result')} | {test_summary} |"
f' {status_icon} |')

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.

medium

If any of the expected keys (like platform, build_result, etc.) are missing from the JSON fragment, d.get(...) will return None, which will print as the string 'None' in the Markdown table. Providing fallback defaults (like 'Unknown' or '-') improves the visual presentation of the report.

Suggested change
markdown_lines.append(
f"| {d.get('platform')} | {d.get('build_result')} |"
f" {d.get('on_host_test_result')} |"
f" {d.get('on_device_test_result')} | {test_summary} |"
f' {status_icon} |')
markdown_lines.append(
f"| {d.get('platform', 'Unknown')} | {d.get('build_result', '-')} |"
f" {d.get('on_host_test_result', '-')} |"
f" {d.get('on_device_test_result', '-')} | {test_summary} |"
f' {status_icon} |')
- completed

permissions:
statuses: write
generate-report:
runs-on: ubuntu-latest
steps:
- name: Checkout
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Comment on PR
if: always() && github.event.workflow_run.event == 'pull_request' && steps.collate.outputs.status != 'PENDING' && steps.collate.outputs.status != 'SKIPPED' && hashFiles('combined_report.md') != ''
uses: marocchino/sticky-pull-request-comment@v2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants