Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions plugins/tend-ci-runner/skills/ci-fix/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,35 @@ gh issue list --state open --author "$BOT_LOGIN" --search "ci-fix: in:title" \
--json number,title,body --limit 10
```

#### Sibling sessions on the same failed run

A consumer that keeps a nightly-failure tracker issue opens it before this session starts, and `tend-triage` fires on that issue within seconds of `tend-ci-fix` firing on the run. Both sessions then diagnose the same failure without knowing the other exists. The searches above can't see it: they match `ci-fix: in:title`, this skill's own artifacts, while the tracker is titled whatever the consumer's template says.

Search by the **failed run id** instead, across any state — a sibling may have already diagnosed and closed:

```bash
RUN_ID=<failed run id>
gh api "repos/$GITHUB_REPOSITORY/issues?state=all&sort=updated&since=$(date -u -d '2 hours ago' +%Y-%m-%dT%H:%M:%SZ)&per_page=100" \
--jq "[.[] | select(.pull_request == null)
| select((.body // \"\") | contains(\"/runs/$RUN_ID\"))] | map({number, title, state})"
```

Three things that recipe depends on:

- **Assign `RUN_ID`.** Unset, the filter degrades to `contains("/runs/")` and matches any issue citing any run — every session would defer to an unrelated thread.
- **`select(.pull_request == null)`.** This endpoint returns pull requests as well as issues, and step 3's PR body links the failed run, so a fix PR matches the same filter. PRs are already handled at the top of step 1, and `Fixes #<n>` aimed at a PR number is silently ignored.
- **The list endpoint, not `gh search issues`.** Search indexing lags minutes behind; the sibling started seconds ago.

`since` and `sort=updated` both key on update time — a tracker reused across a failure streak (`update_existing: true`) has a stale creation date, and the endpoint's default `sort=created` would let it fall off the single page of 100 on a busy repo.

A hit means a sibling owns this failure's issue thread. Then:

- **Don't open your own diagnostic issue** (3a/3b) — comment the diagnosis on that issue, and only if it adds something the sibling didn't post.
- **Check what the sibling already did** before repeating it — a rerun it triggered is already in flight, and both sessions polling the same job to completion doubles the cost for one result.
- **A fix PR is still yours to open** if the cause is durable; reference the sibling issue with `Fixes #<n>`.

This is defer-on-discovery, not a race-free gate — the two sessions start ~2 s apart. It works because the tracker exists before either one does.

### 2. Diagnose and fix

1. Get failure logs: `gh run view <run-id> --log-failed`
Expand Down Expand Up @@ -78,7 +107,7 @@ Automated fix for [failed run](run-url)

If the diagnosis identifies the failure as transient — runner-disk corruption, an isolated network blip, an upstream incident that has since resolved — there is no fix PR to create. Don't post the diagnosis as a commit comment (it surfaces on whatever commit triggered CI, including release commits where it's visibly off-topic).

Instead, open an issue with the diagnosis and close it immediately. The closure records "diagnosed, no further action" while keeping the analysis discoverable and off the commit timeline. Apply the `tend-outage` label — the workflow-level `if:` in `tend-triage` and `tend-mention` skip labelled issues, suppressing the no-op cascade runs (`opened` → silent-exit; `closed`-comment → silent-exit) that would otherwise fire on every transient tracker:
Instead, open an issue with the diagnosis and close it immediately — unless step 1 found a sibling's issue for this run, in which case comment there. The closure records "diagnosed, no further action" while keeping the analysis discoverable and off the commit timeline. Apply the `tend-outage` label — the workflow-level `if:` in `tend-triage` and `tend-mention` skip labelled issues, suppressing the no-op cascade runs (`opened` → silent-exit; `closed`-comment → silent-exit) that would otherwise fire on every transient tracker:

```bash
gh label create tend-outage --description "Tracks bot outage incidents" --color "d93f0b" 2>/dev/null || true
Expand Down Expand Up @@ -113,7 +142,7 @@ If the diagnosis identifies a durable root cause but a safe fix can't be produce

Leave the issue **open**. A subsequent fix PR closes it via `Fixes #<n>` in the PR body (see step 1 — search for a matching open tracking issue before opening the fix PR). This mirrors the consumer-side `create-issue-on-nightly-failure` pattern and gives maintainers a durable "still broken" signal until a fix ships.

**Dedup first.** Search for an open tracking issue covering the same failure shape; if one exists, comment with the new run link rather than opening a duplicate. Match by failure shape (workflow name + diagnostic snippet), not run ID — each run ID is unique and won't dedup:
**Dedup first.** If step 1's run-id search found a sibling's issue, comment there and stop. Otherwise search for an open tracking issue covering the same failure shape; if one exists, comment with the new run link rather than opening a duplicate. Match by failure shape (workflow name + diagnostic snippet), not run ID — a *recurrence* of the same cause carries a different run ID and won't dedup on it:

```bash
BOT_LOGIN=$(gh api user --jq '.login')
Expand Down
2 changes: 2 additions & 0 deletions plugins/tend-ci-runner/skills/running-in-ci/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,8 @@ If you can't find source evidence for a specific detail, say so ("I'm not sure o

**"Likely" is a stop-sign.** A hedge in a user-facing claim — "likely works", "probably parses as", "should behave like", "I think" — means it rests on an unverified guess. Two options: verify and replace the hedge with the answer, or hedge explicitly ("I haven't tested this — would appreciate if you can confirm") and don't dress up the guess as analysis. The shape is the tell, not the exact words: posting an unverified guess as confident-sounding analysis is the hallucination that erodes trust the fastest.

**Don't name who reran a job unless you read it.** A rerun you didn't trigger invites an inferred actor — "the maintainer's rerun (attempt 2) passed" — when the likelier cause is a sibling bot session on the same failure. The API doesn't rescue a guess either: on `repos/{owner}/{repo}/actions/runs/<id>`, `.actor` stays the **original** run's actor (for a `schedule` run, the repo owner) while `.triggering_actor` is who reran it — so the field that reads like "who did this" is the stale one. Read `.triggering_actor` before attributing, or drop the actor: "attempt 2 passed on the same commit" carries the whole evidentiary weight and needs no name.

**Never ship literal placeholders in user-visible content.** Strings like `<PLACEHOLDER>`, `PR #PLACEHOLDER`, `<SHA>`, `TBD`, `XXX`, or `<TODO(fill)>` in an issue body, PR body, or comment are corruption: a deferred substitution that never ran. They survive into the rendered output and read as broken. When a multi-step ask references an artifact that doesn't yet exist ("file an issue that references the PR I'm about to file"), sequence the work so the referenced artifact exists before the referencing body is composed: create the PR → read its number → compose the issue with the number filled in → file the issue. If the cross-reference can't be resolved before posting (e.g. the artifact is out of scope or deferred), omit it or rephrase ("a follow-up PR will…") rather than emit a placeholder. Before any `gh issue create`, `gh pr create`, or `gh ... comment --body-file`, grep the body file for `PLACEHOLDER`, `<SHA>`, `<TODO`, `TBD`, `XXX` and refuse to post if any match. A session that times out mid-sequence leaves an unsubstituted placeholder permanently visible — pre-substitute, don't post-substitute.

### Distinguish transient incidents from durable bugs
Expand Down
Loading