Skip to content

Workspaces 2/10: standalone persistence and agent recovery #1047

Workspaces 2/10: standalone persistence and agent recovery

Workspaces 2/10: standalone persistence and agent recovery #1047

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-review
on:
pull_request_target:
types: [opened, synchronize, ready_for_review, reopened]
jobs:
review:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
# Preserve pending PR events within GitHub's queue limit: a push must not
# replace a ready-for-review run. Runs stay serial so outward actions
# cannot race. An override setting `cancel-in-progress: true` must set
# `queue: null` too — GitHub rejects the pair as an invalid workflow file.
queue: max
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
- name: React with eyes
if: steps.tend_enabled.outputs.enabled == 'true'
run: |
gh api "repos/$REPO/$TARGET/reactions" -f content=eyes --silent \
|| echo "::warning::could not add the eyes reaction"
env:
REPO: ${{ github.repository }}
TARGET: issues/${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}
# The runner checkout remains on the reviewed base tree for local setup
# actions and their POST chains. The harness creates and selects the PR
# topology in an independent disposable clone.
- uses: actions/checkout@v7
if: steps.tend_enabled.outputs.enabled == 'true'
with:
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.TEND_BOT_TOKEN }}
- 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: review
base_branch: ${{ github.event.pull_request.base.ref }}
prompt: |2
/tend-ci-runner:review ${{ github.event.pull_request.number }}
- name: Remove the eyes reaction
if: |
always()
&& (steps.tend_enabled.outputs.enabled == 'true')
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: issues/${{ github.event.pull_request.number }}
BOT_NAME: dormouse-bot
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}