name: nightly description: Nightly code quality sweep — resolves bot PR conflicts, reviews recent commits, surveys existing code, checks resolved issues, and updates tend workflows. metadata: internal: true
Nightly Code Quality Sweep
Resolve conflicts on bot PRs, review recent commits, survey a slice of existing code/docs, and update tend workflows.
Step 0: Load environment skills
Load /tend-ci-runner:running-in-ci first — it contains CI security rules,
polling conventions, and comment formatting guidance. It will also prompt you
to load any repo-specific skills (e.g., running-tend).
Step 1: Verify bot PAT scopes
Run the scope audit script to check the bot PAT against tend's required classic OAuth scopes (repo, workflow, notifications, write:discussion, gist, user):
${CLAUDE_PLUGIN_ROOT}/scripts/pat-scope-audit.sh
The script prints key=value lines. Act on STATUS:
STATUS=ok: all scopes present. Search open issues for a PAT scope audit tracking issue (gh issue list --state open --search "PAT in:title"); if found, close it with a comment noting the scopes are now granted.STATUS=fine-grained: noX-OAuth-Scopesheader. Fine-grained PATs have no documented self-introspection endpoint — skip.STATUS=missing: open or update a tracking issue. Use a title containing "PAT" (e.g.Bot PAT: missing scopes) so future runs can dedup by title search. Before creating, rungh issue list --state open --search "PAT in:title"and update the existing issue withgh issue editif one is already open. The body lists the values fromMISSING=, names the secret to update by its real name (thesecrets.bot_tokenvalue from.config/tend.yaml, defaultTEND_BOT_TOKEN— never a placeholder), and links step 8 of theinstall-tendskill for remediation: https://github.com/max-sixty/tend/blob/main/plugins/install-tend/skills/install-tend/SKILL.md#8-bot-token-and-secret
Step 2: Check tend configuration drift
Run tend check to verify this repo's tend setup (branch protection, bot
permission, secrets, secret allowlist):
uvx tend@latest check 2>&1 | tee /tmp/tend-check.txt
If every check line is PASS (no FAIL and no SKIP), close any
open drift issue. A run with only SKIP lines (e.g. lost API permission, a
transient gh error) is not a pass — leave the issue untouched, neither
close nor file. Scope to bot-authored issues so a maintainer-filed issue
that happens to contain "configuration drift" is never auto-closed:
gh issue list --state open --author '@me' \
--search '"configuration drift" in:title' \
--json number --jq '.[].number' \
| xargs -r -I {} gh issue close {} --comment 'tend check now passes.'
If any check is FAIL, file or update one tracking issue with title
tend check: configuration drift on <owner>/<repo>. Dedup by title,
scoped to bot-authored issues:
gh issue list --state open --author '@me' \
--search '"configuration drift" in:title' \
--json number,title,body
No labels. Body lists the current FAIL lines (one bullet per check, with
a one-line reason) plus a _Last refreshed: <YYYY-MM-DD>_ footer. Updates:
- Failure set identical to the open issue → edit body (refresh footer) only, no comment.
- Failure set changed → edit body to match current state and post a comment describing the delta (added/removed/changed checks).
- No open issue → create one.
Step 3: Resolve conflicts on bot PRs
Find conflicted PRs from this bot and from upstream dependency bots:
BOT_LOGIN=$(gh api user --jq '.login')
for author in "$BOT_LOGIN" app/dependabot app/renovate; do
gh pr list --author "$author" --json number,title,mergeable,headRefName,author \
--jq '.[] | select(.mergeable == "CONFLICTING")'
done
Skip the rest of this step if none of the queries return anything.
Upstream dependency bots: trigger the bot's own rebase
dependabot[bot] and renovate[bot] both attempt rebases on their own, but stop once main rewrites a file the bump also touched (Cargo.lock, uv.lock, generated headers). The PR then sits CONFLICTING. Each bot exposes a way to force a fresh rebuild against current main.
For each conflicted PR by one of these bots, first confirm the branch has no human commits — both triggers force-push and would discard local edits. The check compares each commit's author login against the bot's commit-author login (dependabot[bot] or renovate[bot]); note that the PR's .author.login is the App slug (app/dependabot, app/renovate) and does not match — use the literal commit-author login below.
# COMMIT_LOGIN is "dependabot[bot]" or "renovate[bot]" depending on the bot
gh pr view <number> --json commits \
| jq --arg bot "$COMMIT_LOGIN" \
'[.commits[].authors[].login] | unique | map(select(. != $bot))'
- Empty → trigger the bot per the table below.
- Non-empty → skip. Leave the PR for manual resolution; the force-push would throw away the human commits.
--author (PR list) |
Commit-author login | Trigger |
|---|---|---|
app/dependabot |
dependabot[bot] |
Post @dependabot recreate as a comment. |
app/renovate |
renovate[bot] |
Edit the PR body and replace - [ ] <!-- rebase-check --> with - [x] <!-- rebase-check -->. Renovate has no comment command for rebase. |
Do not check out or rebase manually — the bot owns the branch and will overwrite anything you push.
Bot-authored PRs: resolve manually
Set the git identity once in the main session before dispatching — the subagents below git commit --no-edit in fresh worktrees, and as separate invocations they don't reliably load running-in-ci. --global writes to the runner's shared ~/.gitconfig, so the subagents inherit it and don't fail with Author identity unknown. See "Configure git identity before the first commit" in /tend-ci-runner:running-in-ci.
BOT_LOGIN=$(gh api user --jq '.login'); BOT_ID=$(gh api user --jq '.id')
git config --global user.name "$BOT_LOGIN"
git config --global user.email "${BOT_ID}+${BOT_LOGIN}@users.noreply.github.com"
For each conflicted PR authored by $BOT_LOGIN, dispatch a subagent to:
- Check out the PR:
gh pr checkout <number> - Merge the default branch:
git merge origin/main - Resolve conflicts (read files, understand both sides),
git add,git commit --no-edit - Push, then wait for CI per CI Monitoring in
/tend-ci-runner:running-in-ci - If conflicts are too complex,
git merge --abortand comment explaining manual resolution is needed
Run subagents in parallel. Each must work in isolation (git worktree add /tmp/pr-<number> <branch>). After all complete, clean up temp worktrees.
Step 4: Review recent commits
git log --since='24 hours ago' --oneline main
If no commits in the past 24 hours, skip this step.
Get the aggregate diff:
OLDEST=$(git log --since='24 hours ago' --format='%H' main | tail -1)
git diff ${OLDEST}^..HEAD
git log --since='24 hours ago' --format='%h %s' main
Read the project's CLAUDE.md before reviewing. Apply the review checklist below to the diff, focusing on changes rather than unchanged code. Also check whether CLAUDE.md itself needs updating to reflect the new code (e.g., new file paths, changed commands, removed patterns).
Step 5: Check existing issues
gh issue list --state open --json number,title
gh pr list --state open --json number,title,headRefName
For each open issue, check whether recent commits or the current codebase state already resolve it. If resolved, comment with the evidence (commits, CI runs, or code state that resolves the issue). Close the issue with gh issue close when:
- The bot opened the issue itself to report a transient condition (e.g., a "Nightly tests failed" report from a prior run) and the condition has clearly resolved — the fix PR is merged and the relevant CI on
mainis passing. Skip this case if the issue body contains "Do not close manually"; those are recurring tracking issues (e.g., monthly review-runs trackers) with their own lifecycle. - The repo's guidance (e.g.,
running-tendskill) explicitly authorizes closing issues.
Otherwise, leave it open for a maintainer to close.
Enrich tend-outage issues
The action's "Report failure" step records only a workflow run link in tend-outage issues — annotations and job logs aren't reliably available while the job is in_progress. Run the enrichment script to fetch failure details for each newly referenced run and post them as a comment. The script is idempotent: it skips runs already marked with <!-- enriched-run:RUN_ID -->.
"${CLAUDE_PLUGIN_ROOT}/scripts/enrich-tend-outage-issues.sh"
Step 6: Rolling survey
Run the survey script to get today's file list (rotating through the full repo over 28 days):
${CLAUDE_PLUGIN_ROOT}/scripts/nightly-survey-files.sh
Skip files that aren't meaningfully reviewable: lock files (uv.lock, Cargo.lock, package-lock.json), binary assets, vendored dependencies, and generated files (build output, compiled protobuf, auto-generated workflow YAML). When unsure, check the file — a quick glance is cheaper than missing something.
Before reviewing files, read the project's CLAUDE.md and any project-specific skills or review criteria it references. Apply the review checklist below to each file in full.
Review checklist
Used by both Step 4 (applied to recent diffs) and Step 6 (applied to full files).
General quality:
- Bugs, logic errors, unhandled edge cases
- Dead code, unused imports, unreachable branches
- Simplification opportunities — unnecessary abstractions, indirection, or complexity
- Stale or incorrect documentation (comments, docstrings that no longer match behavior)
- Missing test coverage for non-trivial logic
Convention compliance (from CLAUDE.md and project skills):
- Code patterns that violate conventions stated in the project's CLAUDE.md
- Stale CLAUDE.md entries — conventions that reference renamed files, deleted functions, or outdated patterns
- Skills that have drifted from actual project behavior (instructions that no longer match how the code works)
Step 7: Update tend workflows
Regenerate the tend workflow files and open a PR if anything changed. The checkout's .github/ directory may be mounted read-only under the sandbox (protecting bots from modifying their own workflows in place), so do the regeneration in a git worktree under /tmp, which is writable. Use the literal path /tmp/tend-update-workflows — GitHub Actions runners leave $TMPDIR unset, so a $TMPDIR/... path expands to an unwritable root path.
# Base the worktree on the update-workflows branch only when an **open PR**
# rides it, so the regen produces only the incremental delta. Otherwise base
# on HEAD. Gate on the open PR, not on branch-ref existence: a PR closed without
# merge leaves the branch behind, and basing on that stale branch carries the
# closed PR's content on top of main — inflating the diff and producing an
# inaccurate PR body, and defeating the no-value skip below (its "only
# non-stamp diff" test only holds when the diff is computed against the
# true base, not a stale branch's accumulated content).
# When no open PR exists, drop any leftover remote branch so the push starts
# fresh from HEAD. `-B` resets a stale local branch from a prior failed
# attempt rather than rejecting it.
git fetch origin tend/update-workflows 2>/dev/null || true
if gh pr list --head tend/update-workflows --state open --json number --jq '.[0].number' | grep -q .; then
BASE=$(git rev-parse origin/tend/update-workflows)
else
git push origin --delete tend/update-workflows 2>/dev/null || true
BASE=$(git rev-parse HEAD)
fi
git worktree add "/tmp/tend-update-workflows" -B tend/update-workflows "$BASE"
cd "/tmp/tend-update-workflows"
# Capture the stamped tend version before regenerating, so the next bash
# call can report the bump. Header anchor: `# Generated by tend X.Y.Z.`
# Workflows generated before the header stamp existed return an empty
# string; the renderer below then omits the version line rather than
# printing `unknown → X.Y.Z`. Shell state doesn't persist between bash
# calls, so the version is stashed to a temp file.
grep -hoE '^# Generated by tend [0-9]+\.[0-9]+\.[0-9]+' \
.github/workflows/tend-*.yaml 2>/dev/null \
| sed -E 's/^# Generated by tend //' | sort -u | head -1 \
> "/tmp/tend-old-ver"
uvx tend@latest init
# `init` auto-migrates a legacy `.config/tend.toml` → `.yaml` if it finds
# one (verifies parsed equivalence before swapping); `.config/` is checked
# alongside `.github/workflows` so that one-shot upgrade ships in the same
# nightly PR as the regenerated workflows that depend on it.
git status --porcelain .github/workflows .config
# Stamp-only check: if the only diff is the `# Generated by tend X.Y.Z`
# header (e.g. dependabot has already bumped the action refs in a patch
# release), the workflow bodies are unchanged and the existing files are
# still accurate. A header-only PR carries no value — treat it as a no-op.
NON_STAMP_DIFF=$(git diff --no-color .github/workflows .config \
| grep -E '^[+-]' \
| grep -vE '^(\+\+\+|---) ' \
| grep -vE '^[+-]# Generated by tend [0-9]+\.[0-9]+\.[0-9]+\. Regenerate with: uvx tend@latest init$' \
| wc -l)
If git status shows no changes, or NON_STAMP_DIFF is 0, clean up
and skip the PR:
cd -
git worktree remove "/tmp/tend-update-workflows" --force
If files changed, detect the version bump and gather the upstream changes to describe:
OLD_VER=$(cat "/tmp/tend-old-ver")
NEW_VER=$(grep -hoE '^# Generated by tend [0-9]+\.[0-9]+\.[0-9]+' \
.github/workflows/tend-*.yaml 2>/dev/null \
| sed -E 's/^# Generated by tend //' | sort -u | head -1)
TITLE="chore: update tend workflows"
if [ -n "$OLD_VER" ] && [ -n "$NEW_VER" ] && [ "$OLD_VER" != "$NEW_VER" ]; then
TITLE="chore: update tend workflows ($OLD_VER → $NEW_VER)"
# The real "what changed": squash-merge subjects between the two release
# tags. First line of each upstream commit; empty if the call fails, in
# which case the body carries only the version line.
gh api "repos/max-sixty/tend/compare/$OLD_VER...$NEW_VER" \
--jq '.commits[].commit.message | split("\n")[0]' \
> "/tmp/tend-upstream-commits.txt" 2>/dev/null || true
fi
printf '%s\n' "$TITLE" > "/tmp/tend-pr-title"
echo "OLD=$OLD_VER NEW=$NEW_VER compare: https://github.com/max-sixty/tend/compare/$OLD_VER...$NEW_VER"
Compose the PR body with the Write tool at /tmp/tend-update-body.md — describe the upgrade, don't paste a file list (the diff is just mechanical action-ref bumps):
- Open by noting this is the automated nightly regeneration of tend's workflow files — phrase it per-run, or fold it into the version summary.
- Version bumped: add a
**tend version:** OLD → NEWline, then a short Notable changes list — 3–5 bullets summarizing the entries in/tmp/tend-upstream-commits.txt. Rewrite each(#NNN)ref asmax-sixty/tend#NNN— a bare#NNNauto-links to this repo's own issues, not tend's. Filter to consumer-relevant changes only — harness/action behavior, skill updates (review, ci-fix, triage, nightly, etc.), generator output that changes the adopter's workflow files, CI-monitoring guidance. Exclude pure mechanics (chore: regenerate workflows,chore: release, action-pin and lockfile bumps) and tend-internal items that affect only tend's own development or release (e.g. release-publishing workflow, marketing site, integration-test fixtures, internal refactors with no adopter-visible effect). Close with the compare link printed above. If the commits file is empty (the compare call failed), keep just the version line and the compare link. - No version bump (same-version regen): one sentence on what the regen changed (a generator template tweak the committed workflows were lagging). No version line, no commit list.
Then ship it:
TITLE=$(cat "/tmp/tend-pr-title")
git add -A .github/workflows .config
# A fresh /tmp worktree has no git identity; without this the commit fails with
# `Author identity unknown` and an empty branch gets pushed. Idempotent — see
# "Configure git identity before the first commit" in /tend-ci-runner:running-in-ci.
BOT_LOGIN=$(gh api user --jq '.login'); BOT_ID=$(gh api user --jq '.id')
git config --global user.name "$BOT_LOGIN"
git config --global user.email "${BOT_ID}+${BOT_LOGIN}@users.noreply.github.com"
git commit -m "$TITLE"
git push -u origin tend/update-workflows
gh pr create --title "$TITLE" --body-file "/tmp/tend-update-body.md"
cd -
git worktree remove "/tmp/tend-update-workflows" --force
Step 8: Fix findings
Before acting on findings, check for duplicates and existing work:
gh issue list --state open --json number,title
gh pr list --state open --json number,title,headRefName
The default action is a PR, not an issue. If there's a plausible fix, make it — explain uncertainty in the PR description.
For each finding:
- Create a PR — branch, fix, run full test suite, commit, push, create PR, poll CI. Every bug fix must include a regression test that would have failed before the fix. If a test is not feasible (e.g., pure documentation changes), note why in the PR description. When uncertain about the approach, explain the trade-offs in the description.
- Create an issue only when there's no obvious fix — design questions, problems needing maintainer input, or findings requiring investigation beyond what the survey can provide.
Optional steps
Not run by default. Only run a step here when the project's running-tend skill explicitly enables it.
Changelog maintenance
Keep the project's changelog up to date with recent changes. The running-tend skill specifies the changelog file and the branch to push to.
- Find the changelog file. If it doesn't exist, skip — don't create one.
- Check out the changelog branch. Create it from the default branch if it doesn't exist yet.
- Merge the default branch into the changelog branch to stay current. If the merge conflicts, delete the branch, recreate it from the default branch, and start fresh.
- Identify merged PRs and notable commits since the last entry in the changelog.
- Draft entries matching the existing file's style and format.
- Commit and push directly to the changelog branch — no PR needed, the branch is kept ready to merge for the next release.
Step 9: Summary
Report: commits reviewed, files surveyed, findings, actions taken, assessment (clean / minor issues / needs attention).