name: Closed-loop learning (weekly miss triage) # Weekly reminder workflow for the closed-loop learning process documented # in docs/closed-loop-learning.mdx. Runs at 09:00 UTC every Monday and # also supports manual triggering. # # The workflow does not consume the per-user ``~/.opensre/misses.jsonl`` # store directly (that lives on engineer machines, not on the runner). # Its purpose is to: # 1) Open or refresh a tracking issue that nudges the on-call engineer # to run ``opensre misses stats --since 7d`` and then # ``opensre misses export --since 7d --top 10 --out tests/benchmarks/production_misses/``. # 2) Verify the ``misses_command`` CLI surface is still wired and exits # cleanly, so the weekly process is not blocked by a CLI regression. on: schedule: - cron: "0 9 * * 1" workflow_dispatch: {} permissions: contents: read issues: write jobs: smoke: name: Verify opensre misses CLI runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v5 - name: Install uv uses: astral-sh/setup-uv@v8.1.0 with: enable-cache: true cache-dependency-glob: | pyproject.toml uv.lock - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.13" - name: Install dependencies run: uv sync --frozen --extra dev - name: Smoke test misses CLI run: | uv run opensre misses --help uv run opensre misses list --json uv run opensre misses stats --json remind: name: Open weekly triage reminder needs: smoke runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v5 - name: Open weekly tracking issue (close last week's first) uses: actions/github-script@v7 with: script: | // One issue per week. Previous week's tracker is closed before // opening this week's so triage history is per-week (GitHub's // natural unit) rather than one ever-growing comment thread. // Uses the existing "pending triage" label so we do not // auto-create a new label on first run. const week = new Date().toISOString().slice(0, 10); const titlePrefix = "Weekly closed-loop learning triage"; const title = `${titlePrefix} — week of ${week}`; const body = [ "Weekly nudge per docs/closed-loop-learning.mdx.", "", "Steps for this week's on-call:", "- [ ] `opensre misses stats --since 7d`", "- [ ] Review recurring `(alert, taxonomy)` pairs", "- [ ] `opensre misses export --since 7d --top 10 --out tests/benchmarks/production_misses/`", "- [ ] Open a PR labelled `benchmark` with the new scenarios", "- [ ] Trigger the benchmark workflow on the PR branch", ].join("\n"); const { data: openIssues } = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, state: "open", labels: "pending triage", per_page: 100, }); const stale = openIssues.filter( (i) => i.title.startsWith(titlePrefix) && i.title !== title, ); for (const issue of stale) { await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, state: "closed", state_reason: "completed", }); } const existing = openIssues.find((i) => i.title === title); if (existing) { return; } await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title, body, labels: ["pending triage"], });