tend-notifications #6015
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
| # Generated by tend 0.1.24. Regenerate with: uvx tend@latest init | |
| # | |
| # Do not edit this file directly — it will be overwritten on regeneration. | |
| # To customize behavior, edit the relevant skill (for example, | |
| # `running-tend`) in this repo's .claude/skills/ directory, or open an issue at | |
| # https://github.com/max-sixty/tend/issues for changes that need to | |
| # happen upstream in the tend-ci-runner plugin. | |
| name: tend-notifications | |
| on: | |
| schedule: | |
| - cron: "*/15 * * * *" | |
| workflow_dispatch: | |
| jobs: | |
| notifications: | |
| concurrency: | |
| group: tend-notifications | |
| cancel-in-progress: false | |
| if: github.repository_owner == 'max-sixty' | |
| runs-on: ubuntu-24.04 | |
| environment: | |
| name: tend | |
| deployment: false | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: read | |
| issues: write | |
| steps: | |
| - name: Check for unread notifications and conflicted PRs | |
| id: check | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }} | |
| run: | | |
| # shellcheck shell=bash | |
| # Establish the repository's frequent maintenance queue and decide whether the | |
| # agent needs to boot. Inlined into the generated workflow: env in, | |
| # GITHUB_OUTPUT out. | |
| # | |
| # env: GITHUB_REPOSITORY, GITHUB_OUTPUT, GITHUB_TOKEN | |
| # Activity newer than this belongs to an event workflow that may still be | |
| # running. The same cutoff is passed to the agent and, once every older item has | |
| # a semantic outcome, to GitHub's repository-level mark-read endpoint. Newer | |
| # activity therefore cannot be acknowledged by this run. | |
| CUTOFF=$(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%SZ) | |
| echo "cutoff=$CUTOFF" >> "$GITHUB_OUTPUT" | |
| # Watching makes a new issue or PR visible before the bot has participated in | |
| # its thread. The installer sets this too; every poll repeats the idempotent PUT | |
| # so a later settings change is repaired without additional state. | |
| gh api "repos/$GITHUB_REPOSITORY/subscription" -X PUT \ | |
| -F subscribed=true -F ignored=false --silent \ | |
| || echo "::warning::could not enable repository watching; retrying next cycle" | |
| # Capture every unread page at the cutoff. GitHub occasionally returns an HTML | |
| # error page even with a successful status, so validate the slurped page shape. | |
| # A failed fetch leaves the queue untouched for the next scheduled cycle. | |
| ENDPOINT="notifications?before=$CUTOFF&per_page=100" | |
| if PAGES=$(gh api "$ENDPOINT" --paginate --slurp 2>/dev/null) \ | |
| && NOTIFS=$(echo "$PAGES" | jq -ce \ | |
| 'if type == "array" and all(.[]; type == "array") then add // [] else error("invalid pages") end'); then | |
| COUNT=$(echo "$NOTIFS" | jq 'length') | |
| else | |
| COUNT=0 | |
| echo "::warning::notifications fetch failed; queue left for the next cycle" | |
| fi | |
| echo "count=$COUNT" >> "$GITHUB_OUTPUT" | |
| # GitHub computes mergeability lazily after the base moves. UNKNOWN therefore | |
| # means "worth a synchronous local test", not "clean". This is only a cheap | |
| # boot gate; the agent test-merges every candidate before changing a branch. | |
| # Read the newest comments: a deferral is normally the PR's latest activity. | |
| # An older marker can waste boots, but the resolver paginates before acting. | |
| # shellcheck disable=SC2016 # $q is a GraphQL variable, not a shell variable. | |
| if BOT_LOGIN=$(gh api user --jq .login 2>/dev/null) \ | |
| && PRS=$(gh api graphql -f query=' | |
| query($q: String!) { | |
| search(query: $q, type: ISSUE, first: 100) { | |
| nodes { ... on PullRequest { | |
| mergeable headRefOid | |
| comments(last: 100) { nodes { author { login } body } } | |
| } } | |
| } | |
| }' -f q="repo:$GITHUB_REPOSITORY author:$BOT_LOGIN is:pr is:open" \ | |
| --jq '.data.search.nodes' 2>/dev/null) \ | |
| && CONFLICT_COUNT=$(jq -er --arg bot "$BOT_LOGIN" ' | |
| [.[] | |
| | select(.mergeable != "MERGEABLE") | |
| | . as $pr | |
| | "<!-- tend-conflict-deferred head=\($pr.headRefOid) -->" as $marker | |
| | select(any($pr.comments.nodes[]?; | |
| .author.login == $bot | |
| and (((.body // "") | sub("\\s+$"; "") | split("\n") | last) == $marker)) | |
| | not)] | |
| | length' <<<"$PRS"); then | |
| : | |
| else | |
| CONFLICT_COUNT=0 | |
| echo "::warning::bot PR conflict scan failed; retrying next cycle" | |
| fi | |
| echo "conflict_count=$CONFLICT_COUNT" >> "$GITHUB_OUTPUT" | |
| if [ "$COUNT" = "0" ] && [ "$CONFLICT_COUNT" = "0" ]; then | |
| echo "No notification or conflict work — skipping" | |
| else | |
| [ "$COUNT" = "0" ] || \ | |
| echo "$COUNT notification task(s) — proceeding" | |
| [ "$CONFLICT_COUNT" = "0" ] || \ | |
| echo "$CONFLICT_COUNT possible conflicted bot PR(s) — proceeding" | |
| fi | |
| - uses: actions/checkout@v7 | |
| if: steps.check.outputs.count != '0' || steps.check.outputs.conflict_count != '0' || github.event_name == 'workflow_dispatch' | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| token: ${{ secrets.TEND_BOT_TOKEN }} | |
| - uses: ./.github/actions/tend-setup | |
| if: steps.check.outputs.count != '0' || steps.check.outputs.conflict_count != '0' || github.event_name == 'workflow_dispatch' | |
| - uses: max-sixty/tend/claude@0.1.24 | |
| if: steps.check.outputs.count != '0' || steps.check.outputs.conflict_count != '0' || github.event_name == 'workflow_dispatch' | |
| with: | |
| github_token: ${{ secrets.TEND_BOT_TOKEN }} | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| bot_name: worktrunk-bot | |
| model: opus | |
| sandbox_setup: |2 | |
| mkdir -p ~/.local/bin | |
| install -m755 /home/runner/.cargo-install/cargo-insta/bin/cargo-insta ~/.local/bin/ | |
| install -m755 /home/runner/.cargo-install/cargo-nextest/bin/cargo-nextest ~/.local/bin/ | |
| uv tool install --quiet pre-commit | |
| for t in cargo-insta cargo-nextest pre-commit nu nix; do command -v "$t" >/dev/null || { echo "::error::tend sandbox is missing $t; see Sandbox toolchain in .github/CLAUDE.md"; exit 1; }; done | |
| prompt: |2 | |
| /tend-ci-runner:notifications | |
| Notification snapshot cutoff: ${{ steps.check.outputs.cutoff }} | |
| Possible conflicted bot PR count: ${{ steps.check.outputs.conflict_count }} |