Skip to content
Merged
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
75 changes: 3 additions & 72 deletions .github/workflows/tend-ci-fix.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by tend 0.2.7. Regenerate with: uvx tend@latest init
# Generated by tend 0.2.8. 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,
Expand All @@ -15,7 +15,7 @@ on:

jobs:
fix-ci:
if: github.repository_owner == 'diffplug' && contains(fromJSON('["failure", "cancelled"]'), github.event.workflow_run.conclusion)
if: github.repository_owner == 'diffplug' && vars.TEND_ENABLED != 'false' && contains(fromJSON('["failure", "cancelled"]'), github.event.workflow_run.conclusion)
concurrency:
# A red branch fails every push that follows, each on its own commit, so
# one session per branch — not per commit — is what collapses the burst.
Expand All @@ -34,83 +34,14 @@ jobs:
pull-requests: write
actions: read
steps:
- name: Check whether tend is enabled
id: tend_enabled
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api \
-H "Accept: application/vnd.github.raw+json" \
"repos/$GITHUB_REPOSITORY/contents/.config/tend.yaml" \
> "$RUNNER_TEMP/tend.yaml"
ruby - "$RUNNER_TEMP/tend.yaml" <<'RUBY' >> "$GITHUB_OUTPUT"
# Inspect the parsed YAML node so the extra YAML 1.1 boolean words (yes/no/on/off)
# do not diverge from the YAML 1.2 parser used by `tend init`.
require "psych"

path = ARGV.fetch(0)
documents = Psych.parse_stream(File.read(path, mode: "r:bom|utf-8")).children
unless documents.length == 1
abort "tend config must contain exactly one YAML document"
end

mapping = documents.first.root
unless mapping.is_a?(Psych::Nodes::Mapping)
abort "tend config must contain a YAML mapping"
end

def has_yaml_merge_key?(node)
case node
when Psych::Nodes::Mapping
node.children.each_slice(2).any? do |key, value|
(key.is_a?(Psych::Nodes::Scalar) && key.plain && key.value == "<<") ||
has_yaml_merge_key?(key) || has_yaml_merge_key?(value)
end
when Psych::Nodes::Sequence
node.children.any? { |value| has_yaml_merge_key?(value) }
else
false
end
end

if has_yaml_merge_key?(mapping)
abort "tend config: YAML merge keys (<<) are not supported"
end

matches = mapping.children.each_slice(2).select do |key, _value|
key.is_a?(Psych::Nodes::Scalar) && key.value == "enabled"
end
abort "tend config: enabled must appear at most once" if matches.length > 1

value = matches.dig(0, 1)
enabled = true
if value
bool_tag = value.respond_to?(:tag) && value.tag == "tag:yaml.org,2002:bool"
literal = value.value.downcase if value.is_a?(Psych::Nodes::Scalar)
unless value.is_a?(Psych::Nodes::Scalar) &&
(value.plain || bool_tag) &&
["true", "false"].include?(literal)
abort "tend config: enabled must be true or false"
end
enabled = literal == "true"
end

puts "enabled=#{enabled}"

unless enabled
warn "::notice title=Tend disabled::The tend config sets enabled: false; skipping this job"
end
RUBY
- uses: actions/checkout@v7
if: steps.tend_enabled.outputs.enabled == 'true'
with:
ref: main
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.TEND_BOT_TOKEN }}

- uses: max-sixty/tend/claude@0.2.7
if: steps.tend_enabled.outputs.enabled == 'true'
- uses: max-sixty/tend/claude@0.2.8
with:
github_token: ${{ secrets.TEND_BOT_TOKEN }}
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
Expand Down
69 changes: 69 additions & 0 deletions .github/workflows/tend-mention-relay.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Generated by tend 0.2.8. 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-mention-relay
# Review events for tend-mention. Their runs carry `refs/pull/N/merge`, which
# the operational secrets' environment does not admit, so this workflow holds
# no secret: it re-posts each event as a `repository_dispatch`, which starts
# tend-mention on the default branch. It is a workflow of its own because
# GitHub lists every job of a review event's run among the PR's checks, skipped
# jobs included, so the run holds only the job that can execute. Same-repo PRs
# only — the notifications poll covers fork PRs.
on:
pull_request_review:
types: [submitted]
# Not `created`: GitHub also fires `pull_request_review` for every new inline
# comment (probed across the comments endpoint, the replies endpoint, the
# "Add single comment" button, and reviews submitted with inline comments),
# so subscribing would relay each one twice. An edit fires no review event,
# so `edited` stays, to catch "@bot" added to an existing comment.
pull_request_review_comment:
types: [edited]

jobs:
# Move the event onto a ref the environment admits, and nothing else. The
# merge ref itself can never be admitted — a same-repo `pull_request` run
# executes the PR head's own workflow files on that same ref, so admitting
# it would hand a pushed workflow the secrets the environment exists to
# deny. All judgement lives in tend-mention's `verify`, which re-reads the
# review or comment from the API, so a skipped event costs one extra short
# run rather than a second copy of the heuristics here.
relay:
# Fork PRs are excluded to hold long-standing behaviour: the notifications
# poll covers them, and it applies author-association tiers that this path
# does not. What the filter buys is that no fork head is checked out into a
# secret-bearing run; it is not an authorship gate, since a mention from
# any user with read access already wakes `handle` on a same-repo PR.
# Widening this is a separate decision, and moot while a fork run's token
# is refused the dispatch POST (403, probed).
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
# No secrets here. `contents: write` is what the dispatch POST needs, and
# is the whole of what this token can do — probed both ways on a live
# repo: an otherwise identical same-repo run declaring `contents: read`
# is refused with 403, so a narrower token would leave every review
# mention unanswered rather than merely unprivileged. It grants nobody a
# new capability: only someone who can already push a branch can open the
# same-repo PR whose workflow file this is, and they could declare any
# permissions they liked in it. The merge restriction is what bounds that,
# here as everywhere.
permissions:
contents: write
steps:
# Identifiers only: `verify` re-reads the review or comment from the
# API, so the words the bot weighs and acts on are the ones GitHub
# holds, and a forged dispatch faces the same scrutiny as a relayed one.
- name: Re-enter on an admitted ref
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api "repos/$GITHUB_REPOSITORY/dispatches" \
-f event_type=tend-mention-review \
-f "client_payload[kind]=${{ github.event_name }}" \
-f "client_payload[pr]=${{ github.event.pull_request.number }}" \
-f "client_payload[id]=${{ github.event.review.id || github.event.comment.id }}"
Loading