Drift to issue #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Drift to issue | |
| # When a Harness drift sensor fails on main (or on the nightly schedule), | |
| # open or update a single tracking GitHub issue per sensor. This stops | |
| # drift signals from rotting in CI logs — they become actionable work | |
| # items instead. Closes the steering-loop arrow: sensor fires → issue | |
| # exists → human decides whether to fix or accept the drift. | |
| # | |
| # Only fires on main / scheduled runs, not on PRs (the in-PR annotation | |
| # from harness.yml is enough there — we don't want every PR to open | |
| # issues). | |
| on: | |
| workflow_run: | |
| workflows: ['Harness'] | |
| types: [completed] | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| issues: write | |
| jobs: | |
| open-or-update-issue: | |
| # No workflow-level conclusion check: Harness jobs use | |
| # `continue-on-error: true`, which means the workflow itself concludes | |
| # `success` even when individual sensors fail. We inspect job-level | |
| # conclusions inside the Python loop below — jobs that pass are a no-op. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Find failed jobs and (re)open issues | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RUN_ID: ${{ github.event.workflow_run.id }} | |
| RUN_URL: ${{ github.event.workflow_run.html_url }} | |
| run: | | |
| set -euo pipefail | |
| jobs_json=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/jobs" --paginate) | |
| # For each failed job, ensure a single open tracking issue | |
| # exists. Title format `[harness-drift] <job name>` makes | |
| # updates idempotent. | |
| echo "$jobs_json" | python3 - <<'PY' | |
| import json, os, subprocess, sys | |
| jobs = json.loads(sys.stdin.read())["jobs"] | |
| run_url = os.environ["RUN_URL"] | |
| for job in jobs: | |
| if job["conclusion"] != "failure": | |
| continue | |
| name = job["name"] | |
| title = f"[harness-drift] {name}" | |
| body = ( | |
| f"The harness drift sensor `{name}` failed on `main`.\n\n" | |
| f"Run: {run_url}\n\n" | |
| "This issue is automatically opened by `.github/workflows/drift-to-issue.yml`.\n" | |
| "It's a *tracking* issue — the sensor will keep firing until either:\n" | |
| " - the underlying drift is resolved (close this issue), or\n" | |
| " - the sensor is intentionally disabled (remove the job from `harness.yml`).\n\n" | |
| "Repeat failures update this same issue rather than opening duplicates." | |
| ) | |
| search = subprocess.run( | |
| ["gh", "issue", "list", "--state", "open", "--label", "harness-drift", | |
| "--search", f'in:title "{title}"', "--json", "number,title"], | |
| capture_output=True, text=True, check=True, | |
| ) | |
| existing = [i for i in json.loads(search.stdout) if i["title"] == title] | |
| if existing: | |
| num = existing[0]["number"] | |
| subprocess.run( | |
| ["gh", "issue", "comment", str(num), "--body", | |
| f"Sensor fired again. Run: {run_url}"], | |
| check=True, | |
| ) | |
| print(f"updated #{num}: {title}") | |
| else: | |
| subprocess.run( | |
| ["gh", "issue", "create", "--title", title, "--body", body, | |
| "--label", "harness-drift"], | |
| check=True, | |
| ) | |
| print(f"opened: {title}") | |
| PY |