worktrunk 0.42.0

A CLI for Git worktree management, designed for parallel AI agent workflows
Documentation
# Generated by tend. 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:
    runs-on: ubuntu-24.04
    permissions:
      contents: write
      pull-requests: write
      id-token: write
      actions: read
      issues: write
    steps:
      - name: Check for unread notifications
        id: check
        run: |
          COUNT=$(gh api notifications --jq 'length')
          if [ "$COUNT" = "0" ]; then
            echo "count=0" >> "$GITHUB_OUTPUT"
            echo "No unread notifications — skipping"
            exit 0
          fi

          # --- Layer B: drop notifications shadowed by recent dedicated runs ---
          # Event workflows mark their own notifications read via action.yaml's
          # post-step on success; this sweeps the case where Claude failed
          # (post-step is gated by `if: success()`) so the notification still
          # gets cleared without burning Claude turns to rediscover it.
          SINCE=$(date -u -d '30 minutes ago' +%Y-%m-%dT%H:%M:%SZ)
          RECENT_PRS=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs?created=>=$SINCE&per_page=50" --jq '[.workflow_runs[] | select(.name | test("^(tend-review|tend-mention|tend-triage|tend-ci-fix)$")) | .pull_requests[]?.number] | unique | .[]' || true)

          if [ -n "$RECENT_PRS" ]; then
            NOTIFS=$(gh api notifications)
            for pr in $RECENT_PRS; do
              echo "$NOTIFS" | jq -r --arg repo "$GITHUB_REPOSITORY" --arg pr "$pr" '.[] | select(.subject.url == "https://api.github.com/repos/" + $repo + "/pulls/" + $pr or .subject.url == "https://api.github.com/repos/" + $repo + "/issues/" + $pr) | .id' | while read -r tid; do
                [ -n "$tid" ] || continue
                gh api "notifications/threads/$tid" -X PATCH || true
              done
            done
          fi

          # --- Layer C: drop notifications on bot-authored closed PRs ---
          # The bot auto-subscribes to its own PRs. After merge/close, leftover
          # subscription notifications are pure noise — no action needed.
          NOTIFS=$(gh api notifications)
          echo "$NOTIFS" | jq -r --arg repo "$GITHUB_REPOSITORY" '.[] | select(.repository.full_name == $repo and .subject.type == "PullRequest") | .id' | while read -r tid; do
            [ -n "$tid" ] || continue
            PR_NUM=$(echo "$NOTIFS" | jq -r --arg tid "$tid" '.[] | select(.id == $tid) | .subject.url | split("/") | last')
            PR_INFO=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUM" --jq '"\(.user.login) \(.state)"' 2>/dev/null) || continue
            PR_AUTHOR=${PR_INFO%% *}
            PR_STATE=${PR_INFO##* }
            if [ "$PR_AUTHOR" = "worktrunk-bot" ] && [ "$PR_STATE" = "closed" ]; then
              gh api "notifications/threads/$tid" -X PATCH || true
            fi
          done

          # --- Layer D: count processable notifications ---
          # Same-repo notifications younger than 10 minutes are deferred: a
          # dedicated workflow (tend-review/mention/triage/ci-fix) is likely
          # still starting up or mid-flight and hasn't posted its response yet.
          # Processing them now risks duplicating work. Cross-repo notifications
          # are exempt — no dedicated workflow handles them.
          CUTOFF=$(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%SZ)
          REMAINING=$(gh api notifications)
          COUNT=$(echo "$REMAINING" | jq --arg repo "$GITHUB_REPOSITORY" --arg cutoff "$CUTOFF" '[.[] | select(.repository.full_name != $repo or .updated_at <= $cutoff)] | length')
          echo "count=$COUNT" >> "$GITHUB_OUTPUT"
          if [ "$COUNT" = "0" ]; then
            TOTAL=$(echo "$REMAINING" | jq 'length')
            if [ "$TOTAL" = "0" ]; then
              echo "All notifications handled by pre-checks — skipping"
            else
              echo "$TOTAL notification(s) remain but all are fresh same-repo (deferred) — skipping"
            fi
          else
            echo "$COUNT processable notification(s) — proceeding"
          fi
        env:
          GH_TOKEN: ${{ secrets.WORKTRUNK_BOT_TOKEN }}

      - uses: actions/checkout@v6
        if: steps.check.outputs.count != '0' || github.event_name == 'workflow_dispatch'
        with:
          ref: main
          fetch-depth: 0
          fetch-tags: true
          token: ${{ secrets.WORKTRUNK_BOT_TOKEN }}

      - uses: ./.github/actions/claude-setup
        if: steps.check.outputs.count != '0' || github.event_name == 'workflow_dispatch'
      - uses: max-sixty/tend@v1
        if: steps.check.outputs.count != '0' || github.event_name == 'workflow_dispatch'
        with:
          github_token: ${{ secrets.WORKTRUNK_BOT_TOKEN }}
          claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          bot_name: worktrunk-bot
          model: opus
          prompt: |
            /tend-ci-runner:notifications