Skip to content

Workspaces 9/10: dor workspace move, dor list --window, iframe move gate #4117

Workspaces 9/10: dor workspace move, dor list --window, iframe move gate

Workspaces 9/10: dor workspace move, dor list --window, iframe move gate #4117

Workflow file for this run

# Generated by tend 0.2.5. 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
on:
issues:
types: [edited]
issue_comment:
types: [created, edited]
# The review events reach only the `relay` job below: their runs carry
# `refs/pull/N/merge`, which the operational secrets' environment does not
# admit, so the secret-bearing jobs see them re-entered as a
# `repository_dispatch` instead. Same-repo PRs only — the notifications poll
# covers fork PRs.
pull_request_review:
types: [submitted]
# `created` is intentionally absent. Modern GitHub fires *both*
# pull_request_review and pull_request_review_comment for every newly-created
# inline comment (the standalone POST /pulls/PR/comments endpoint, the
# /replies endpoint, the "Add single comment" UI button, and reviews
# submitted with inline comments — all empirically verified). Subscribing to
# `created` would produce a duplicate dispatch whose handle runs collide on
# the tend-mention-handle-PR concurrency group, with the loser cancelled and
# posted as a CANCELLED check_run on the PR head SHA — which renders the
# PR's statusCheckRollup as FAILURE even though the bot did its job from the
# sibling run. Edits have no sibling event (review submissions don't fire on
# edits), so we still need to listen for `edited` to catch edit-to-summon
# ("@bot" added to an existing comment after the fact).
pull_request_review_comment:
types: [edited]
# The relay's re-entry point. GitHub creates this run even though a
# `GITHUB_TOKEN` triggered it — `workflow_dispatch` and `repository_dispatch`
# are the two events exempt from the rule that token-triggered events start
# no workflow — and it carries the default branch, which the environment
# admits.
repository_dispatch:
types: [tend-mention-review]
jobs:
# The secretless half of the review path: 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 `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). A dispatched run fails the
# event check, so the relay cannot re-enter itself.
if: |
(github.event_name == 'pull_request_review' ||
github.event_name == 'pull_request_review_comment') &&
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:
- 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
# 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
if: steps.tend_enabled.outputs.enabled == 'true'
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 }}"
verify:
# Skip comments on the issues tend files about its own health: the action
# auto-comments on those when a run fails or is refused, and without this
# guard those comments re-trigger tend-mention, producing a
# self-sustaining ~1 run/minute loop until the underlying condition
# clears. The prompt's self-loop guard can't help here because the model
# never executes — the action fails before Claude starts. A relayed review
# enters as `repository_dispatch` and is judged in the check step below,
# against the record the API holds.
if: |
github.event_name == 'repository_dispatch' ||
(github.event_name == 'issues' &&
contains(github.event.issue.body, '@dormouse-bot')) ||
(github.event_name == 'issue_comment' &&
contains(github.event.issue.labels.*.name, 'tend-outage') == false && contains(github.event.issue.labels.*.name, 'tend-rate-limit') == false)
runs-on: ubuntu-24.04
environment:
name: tend
deployment: false
permissions:
contents: read
outputs:
should_run: ${{ steps.check.outputs.should_run }}
reason: ${{ steps.check.outputs.reason }}
url: ${{ steps.check.outputs.url }}
ts: ${{ steps.check.outputs.ts }}
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: astral-sh/setup-uv@v10.0.1
if: steps.tend_enabled.outputs.enabled == 'true'
with:
version: "0.12.10"
ignore-empty-workdir: true
- name: Verify bot engagement
id: check
if: steps.tend_enabled.outputs.enabled == 'true'
run: |
uv run --script - <<'TEND_PY'
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
"""Decide whether a mention event should start an agent session."""
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any
def gh(*args: str, quiet: bool = False) -> str:
result = subprocess.run(["gh", *args], capture_output=True, text=True, check=False)
if result.returncode:
if result.stderr and not quiet:
sys.stderr.write(result.stderr)
raise subprocess.CalledProcessError(
result.returncode, result.args, result.stdout, result.stderr
)
return result.stdout
def gh_json(*args: str, quiet: bool = False) -> Any:
return json.loads(gh(*args, quiet=quiet))
def gh_paginated(path: str) -> list[dict[str, Any]]:
text = gh("api", "--paginate", path)
decoder = json.JSONDecoder()
position = 0
items: list[dict[str, Any]] = []
while position < len(text):
while position < len(text) and text[position].isspace():
position += 1
if position == len(text):
break
page, position = decoder.raw_decode(text, position)
if not isinstance(page, list):
raise TypeError("paginated GitHub response was not an array")
items.extend(page)
return items
def actor_login(actor: object) -> str:
"""Return a GitHub actor login, including for deleted-account records."""
if not isinstance(actor, dict):
return ""
return str(actor.get("login") or "")
def output(name: str, value: str | bool) -> None:
rendered = str(value).lower() if isinstance(value, bool) else value
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as stream:
stream.write(f"{name}={rendered}\n")
def verdict(should_run: bool, reason: str = "") -> int:
output("should_run", should_run)
if reason:
output("reason", reason)
return 0
def main() -> int:
env = os.environ
bot = env.get("BOT_NAME", "")
repo = env.get("GITHUB_REPOSITORY", "")
kind = env.get("EVENT_NAME", "")
comment_body = env.get("COMMENT_BODY", "")
comment_author = env.get("COMMENT_AUTHOR", "")
review_author = ""
review_state = ""
inline: list[dict[str, Any]] = []
fresh_inline = 0
if kind == "repository_dispatch":
kind = env.get("PAYLOAD_KIND", "")
pr = env.get("PAYLOAD_PR", "")
item_id = env.get("PAYLOAD_ID", "")
if not pr.isdigit() or not item_id.isdigit():
print("malformed dispatch payload — skipping")
return verdict(False)
if kind == "pull_request_review":
try:
review = gh_json(
"api", f"repos/{repo}/pulls/{pr}/reviews/{item_id}", quiet=True
)
except (subprocess.CalledProcessError, json.JSONDecodeError):
print(f"review {item_id} not found on PR {pr} — skipping")
return verdict(False)
review_author = actor_login(review.get("user"))
review_state = str(review["state"]).lower()
comment_body = review.get("body") or ""
output("url", review["html_url"])
output("ts", review.get("submitted_at") or "")
elif kind == "pull_request_review_comment":
try:
comment = gh_json(
"api", f"repos/{repo}/pulls/comments/{item_id}", quiet=True
)
except (subprocess.CalledProcessError, json.JSONDecodeError):
print(f"comment {item_id} not found — skipping")
return verdict(False)
expected_pr = f"https://api.github.com/repos/{repo}/pulls/{pr}"
if comment.get("pull_request_url") != expected_pr:
print(f"comment {item_id} does not belong to PR {pr} — skipping")
return verdict(False)
comment_author = actor_login(comment.get("user"))
comment_body = comment.get("body") or ""
output("url", comment["html_url"])
output("ts", comment["updated_at"])
else:
print(f"unknown dispatch kind '{kind}' — skipping")
return verdict(False)
if kind == "issues":
return verdict(True)
if (
kind in {"issue_comment", "pull_request_review_comment"}
and comment_author == bot
):
return verdict(False)
if comment_body and f"@{bot}" in comment_body:
return verdict(True, "mention")
if kind == "issue_comment" and env.get("COMMENT_AUTHOR_TYPE") == "Bot":
return verdict(False)
if kind == "pull_request_review":
inline = gh_paginated(
f"repos/{repo}/pulls/{env.get('PAYLOAD_PR', '')}/reviews/"
f"{env.get('PAYLOAD_ID', '')}/comments"
)
if any(f"@{bot}" in (comment.get("body") or "") for comment in inline):
return verdict(True, "mention")
fresh_inline = sum(comment.get("in_reply_to_id") is None for comment in inline)
if review_state == "approved" and not comment_body and not inline:
return verdict(False)
if kind == "issue_comment":
issue_number = env.get("ISSUE_OR_PR_NUMBER", "")
if not env.get("PR_URL"):
if env.get("ISSUE_AUTHOR") == bot or f"@{bot}" in env.get("ISSUE_BODY", ""):
return verdict(True)
comments = gh_paginated(f"repos/{repo}/issues/{issue_number}/comments")
return verdict(
any(actor_login(comment.get("user")) == bot for comment in comments)
)
pr_number = issue_number
else:
pr_number = env.get("PAYLOAD_PR", "")
pr = gh_json("pr", "view", pr_number, "--repo", repo, "--json", "author")
pr_author = actor_login(pr.get("author"))
if kind == "pull_request_review" and review_author == bot:
if pr_author == bot and (comment_body or fresh_inline > 0):
return verdict(True, "participation")
return verdict(False)
if pr_author == bot:
return verdict(True, "participation")
reviews = gh_paginated(f"repos/{repo}/pulls/{pr_number}/reviews")
if any(actor_login(review.get("user")) == bot for review in reviews):
return verdict(True, "participation")
comments = gh_paginated(f"repos/{repo}/issues/{pr_number}/comments")
if any(actor_login(comment.get("user")) == bot for comment in comments):
return verdict(True, "participation")
return verdict(False)
if __name__ == "__main__":
try:
raise SystemExit(main())
except subprocess.CalledProcessError as error:
raise SystemExit(error.returncode or 1) from None
TEND_PY
env:
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}
BOT_NAME: dormouse-bot
EVENT_NAME: ${{ github.event_name }}
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
COMMENT_AUTHOR_TYPE: ${{ github.event.comment.user.type }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_OR_PR_NUMBER: ${{ github.event.issue.number }}
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
PR_URL: ${{ github.event.issue.pull_request.url }}
PAYLOAD_KIND: ${{ github.event.client_payload.kind }}
PAYLOAD_PR: ${{ github.event.client_payload.pr }}
PAYLOAD_ID: ${{ github.event.client_payload.id }}
handle:
needs: verify
if: needs.verify.outputs.should_run == 'true'
concurrency:
group: ${{ github.workflow }}-handle-${{ github.event.issue.number || github.event.client_payload.pr }}
cancel-in-progress: false
runs-on: ubuntu-24.04
environment:
name: tend
deployment: false
permissions:
contents: write
pull-requests: write
actions: read
issues: write
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
# Both halves of the reaction belong to this job, so the eyes can only
# go on once the job that takes them off has started. Put them in
# `verify` and `handle` respectively and the routine burst case strands
# them: a third mention on one thread evicts the second's pending
# `handle` — a job cancelled while queued allocates no runner and runs
# no steps, `always()` included — while its `verify` already reacted.
#
# The dispatch arm covers a relayed inline comment, whose id the check
# step verified belongs to this PR; a review *submission* has no single
# comment to react to, so it gets no eyes — same as when the events
# arrived directly. The job's own `if` already carries `should_run`.
- name: React with eyes
if: |
(steps.tend_enabled.outputs.enabled == 'true') && (((github.event.comment && contains(github.event.comment.body, '@dormouse-bot'))
|| (github.event.client_payload.kind == 'pull_request_review_comment'
&& needs.verify.outputs.reason == 'mention')))
run: |
gh api "repos/$REPO/$TARGET/reactions" -f content=eyes --silent \
|| echo "::warning::could not add the eyes reaction"
env:
REPO: ${{ github.repository }}
TARGET: ${{ github.event_name == 'issue_comment'
&& format('issues/comments/{0}', github.event.comment.id)
|| format('pulls/comments/{0}', github.event.comment.id || github.event.client_payload.id) }}
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}
- uses: actions/checkout@v7
if: steps.tend_enabled.outputs.enabled == 'true'
with:
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.TEND_BOT_TOKEN }}
- name: Compute queue delay
id: delay
if: steps.tend_enabled.outputs.enabled == 'true'
run: |
if [ -z "$EVENT_TS" ]; then
echo "seconds=" >> "$GITHUB_OUTPUT"
exit 0
fi
event_epoch=$(date -d "$EVENT_TS" +%s)
echo "seconds=$(( $(date +%s) - event_epoch ))" >> "$GITHUB_OUTPUT"
env:
# A relayed event's timestamp comes from verify, which read it off
# the API record — the dispatch payload never carries one to spoof.
EVENT_TS: ${{ github.event.comment.updated_at || needs.verify.outputs.ts || github.event.issue.updated_at }}
- uses: max-sixty/tend/claude@0.2.5
if: steps.tend_enabled.outputs.enabled == 'true'
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: dormouse-bot
model: opus
checkout_mode: mention
base_branch: ${{ github.event.repository.default_branch }}
prompt: >-
${{ steps.delay.outputs.seconds
&& format('This job started {0}s after the triggering event (over ~40s means it was queued). ',
steps.delay.outputs.seconds) || '' }}Before acting,
check recent comments: exit silently if the bot already responded
to the trigger; handle any other unaddressed comments too.
${{ github.event_name == 'issues'
&& format('An issue was updated with a mention of you ({0}). Read it and respond.', github.event.issue.html_url)
|| (github.event.client_payload.kind == 'pull_request_review_comment' && needs.verify.outputs.reason == 'mention'
&& format('You were mentioned in an inline review comment on PR #{0} ({1}, comment ID {2}). Read the full context, then respond. If changes are requested, make them, commit, and push.', github.event.client_payload.pr, needs.verify.outputs.url, github.event.client_payload.id))
|| (github.event.client_payload.kind == 'pull_request_review_comment'
&& format('An inline review comment was posted on a PR where you previously participated (PR #{0}, {1}, comment ID {2}). Read the full context. Only respond if the comment is directed at you or requests changes.', github.event.client_payload.pr, needs.verify.outputs.url, github.event.client_payload.id))
|| (github.event.client_payload.kind == 'pull_request_review' && needs.verify.outputs.reason == 'mention'
&& format('A review was submitted on PR #{0} that mentions you ({1}, review ID {2}). Read the review and full context, then respond. If changes were requested, make them, commit, and push.', github.event.client_payload.pr, needs.verify.outputs.url, github.event.client_payload.id))
|| (github.event.client_payload.kind == 'pull_request_review'
&& format('A review was submitted on a PR where you previously participated (PR #{0}, {1}, review ID {2}). Read the review and full context. If it requests changes or asks questions, respond appropriately — including when you authored the review: a review your review workflow left on your own PR is your reviewer role speaking, not a self-loop, so action it. Exit silently for a plain approval, a review with no actionable content, or one between other participants.', github.event.client_payload.pr, needs.verify.outputs.url, github.event.client_payload.id))
|| (contains(github.event.comment.body, '@dormouse-bot')
&& format('You were mentioned in a comment ({0}). Read the full context and respond. If changes are requested, make them, commit, and push.', github.event.comment.html_url))
|| format('A user commented on an issue/PR where you previously participated ({0}). Read the full context. Only respond if the comment is directed at you, asks a question you can help with, or requests changes you can make. If the conversation is between other participants, exit silently.', github.event.comment.html_url)
}}
- name: Remove the eyes reaction
if: |
always()
&& (steps.tend_enabled.outputs.enabled == 'true')
&& (((github.event.comment && contains(github.event.comment.body, '@dormouse-bot'))
|| (github.event.client_payload.kind == 'pull_request_review_comment'
&& needs.verify.outputs.reason == 'mention')))
run: |
REACTION_ID=$(gh api --paginate \
"repos/$REPO/$TARGET/reactions?content=eyes&per_page=100" \
--jq ".[] | select(.user.login == \"$BOT_NAME\") | .id" | head -n1)
if [ -n "$REACTION_ID" ]; then
gh api -X DELETE "repos/$REPO/$TARGET/reactions/$REACTION_ID" --silent \
|| echo "::warning::could not remove the eyes reaction"
fi
env:
REPO: ${{ github.repository }}
TARGET: ${{ github.event_name == 'issue_comment'
&& format('issues/comments/{0}', github.event.comment.id)
|| format('pulls/comments/{0}', github.event.comment.id || github.event.client_payload.id) }}
BOT_NAME: dormouse-bot
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}