mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-14 17:12:30 +02:00
Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.181 to 1.0.183.
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](44423bdec7...be7b93b190)
---
updated-dependencies:
- dependency-name: anthropics/claude-code-action
dependency-version: 1.0.183
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
200 lines
9.1 KiB
YAML
200 lines
9.1 KiB
YAML
---
|
|
# Destination: .github/workflows/on_issue_approved.yaml
|
|
#
|
|
# Tier 3 of the AI triage system — the approval executor.
|
|
#
|
|
# Tier 2 (daily_ai_fix.yaml) posts a full `<!-- ai-plan -->` plan on any issue
|
|
# it is not confident enough to fix unattended, and labels it `ai:plan-pending`.
|
|
# When @alexbelgium reviews that plan and applies the `ai:approved` label, this
|
|
# workflow fires within a minute and executes the plan on Opus, opening a
|
|
# ready-for-review pull request — no waiting for the next daily sweep.
|
|
#
|
|
# Manual precedence: the label is the maintainer's explicit go-ahead, so this
|
|
# runs regardless of `no-ai`. Only @alexbelgium may approve; a label applied by
|
|
# anyone else is stripped and ignored (revoke job below).
|
|
#
|
|
# Auth: CR_PAT environment (CLAUDE_CODE_OAUTH_TOKEN) + AI_PR_TOKEN (so the PR
|
|
# triggers onpr_check-pr.yaml, which GITHUB_TOKEN-authored PRs would not).
|
|
# Kill switch: repo variable AI_DISABLED=true pauses this (and every AI workflow).
|
|
|
|
name: AI approved-plan executor
|
|
|
|
on:
|
|
issues:
|
|
types: [labeled]
|
|
workflow_dispatch:
|
|
inputs:
|
|
issue:
|
|
description: "Issue number whose approved plan to execute"
|
|
required: true
|
|
|
|
permissions:
|
|
contents: write
|
|
issues: write
|
|
pull-requests: write
|
|
|
|
concurrency:
|
|
group: ai-approve-${{ github.event.issue.number || inputs.issue }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
# A non-maintainer applied ai:approved: strip it and do nothing else. Only
|
|
# users with triage rights can label at all, so this is belt-and-braces.
|
|
revoke:
|
|
if: >-
|
|
github.event_name == 'issues' &&
|
|
github.event.label.name == 'ai:approved' &&
|
|
github.event.sender.login != 'alexbelgium'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
issues: write
|
|
steps:
|
|
- name: Remove unauthorised approval
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
ISSUE: ${{ github.event.issue.number }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh issue edit "$ISSUE" --repo "$REPO" --remove-label ai:approved || true
|
|
gh issue comment "$ISSUE" --repo "$REPO" --body \
|
|
"The \`ai:approved\` label only takes effect when applied by @alexbelgium; removing it."
|
|
|
|
execute:
|
|
# workflow_dispatch is a maintainer override, so it must also be gated to
|
|
# @alexbelgium — otherwise any collaborator with run-workflow rights could
|
|
# execute a Tier 3 plan without the approval label or sender check.
|
|
if: >-
|
|
vars.AI_DISABLED != 'true' &&
|
|
( ( github.event_name == 'workflow_dispatch' &&
|
|
github.actor == 'alexbelgium' ) ||
|
|
( github.event.label.name == 'ai:approved' &&
|
|
github.event.sender.login == 'alexbelgium' ) )
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
environment: CR_PAT
|
|
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
token: ${{ secrets.AI_PR_TOKEN }}
|
|
|
|
- name: Configure git
|
|
run: |
|
|
git config user.name "claude-ai-fix[bot]"
|
|
git config user.email "claude-ai-fix[bot]@users.noreply.github.com"
|
|
|
|
- name: Ensure control labels exist
|
|
env:
|
|
GH_TOKEN: ${{ secrets.AI_PR_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
for l in ai:fixed ai:plan-pending ai:approved ai:needs-human ai:blocked; do
|
|
gh label create "$l" --repo "$REPO" --color ededed --force >/dev/null 2>&1 || true
|
|
done
|
|
|
|
- name: Bundle the approved plan
|
|
id: bundle
|
|
env:
|
|
GH_TOKEN: ${{ secrets.AI_PR_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p /tmp/ai-exec
|
|
gh issue view "$ISSUE" --repo "$REPO" \
|
|
--json number,title,body,author,labels,comments > /tmp/ai-exec/issue.json
|
|
# The plan is the most recent ai-plan comment FROM A TRUSTED AUTHOR.
|
|
# The marker alone is not proof of origin: any reporter can paste
|
|
# "<!-- ai-plan -->" into a comment, and picking it by marker+last
|
|
# would let them swap in a plan that then executes on approval.
|
|
# Tier 2 posts under AI_PR_TOKEN, whose identity is a repo OWNER/
|
|
# MEMBER/COLLABORATOR; a reporter is never one of those. (If you
|
|
# switch AI_PR_TOKEN to a GitHub App, add its bot login here.)
|
|
jq -r '[.comments[]
|
|
| select((.body | contains("<!-- ai-plan -->"))
|
|
and (.authorAssociation == "OWNER"
|
|
or .authorAssociation == "MEMBER"
|
|
or .authorAssociation == "COLLABORATOR"))]
|
|
| last | .body // ""' /tmp/ai-exec/issue.json > /tmp/ai-exec/plan.md
|
|
if [ ! -s /tmp/ai-exec/plan.md ]; then
|
|
echo "has_plan=false" >> "$GITHUB_OUTPUT"
|
|
echo "::warning::no trusted <!-- ai-plan --> comment on issue #$ISSUE; nothing to execute"
|
|
# Consume the approval here too, so a later real plan can be
|
|
# re-approved (re-adding ai:approved to an issue that still carries
|
|
# it would not fire a fresh labeled event).
|
|
gh issue edit "$ISSUE" --repo "$REPO" --remove-label ai:approved >/dev/null 2>&1 || true
|
|
gh issue comment "$ISSUE" --repo "$REPO" --body \
|
|
"No AI plan (\`<!-- ai-plan -->\`) from the triage bot was found on this issue, so \`ai:approved\` has nothing to execute (removed). Run the tier-2 sweep on it first (\`AI fix sweep\` → issue $ISSUE), then approve the plan it posts."
|
|
else
|
|
echo "has_plan=true" >> "$GITHUB_OUTPUT"
|
|
echo "plan: $(wc -c < /tmp/ai-exec/plan.md) bytes"
|
|
fi
|
|
|
|
- name: Execute the plan
|
|
if: steps.bundle.outputs.has_plan == 'true'
|
|
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
|
with:
|
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
|
# github.actor lacks write access. AI_PR_TOKEN, not GITHUB_TOKEN, so
|
|
# a PR Claude opens triggers CI.
|
|
github_token: ${{ secrets.AI_PR_TOKEN }}
|
|
# Only the `issues` path has a comment thread to track progress in.
|
|
# On workflow_dispatch there is none, and passing true there fails
|
|
# the action's input validation outright.
|
|
track_progress: ${{ github.event_name == 'issues' }}
|
|
prompt: |
|
|
The approved plan is /tmp/ai-exec/plan.md and the issue it belongs
|
|
to is /tmp/ai-exec/issue.json. Follow .github/prompts/issue-execute-plan.md
|
|
exactly. Do not deviate from the path restrictions under any
|
|
circumstances.
|
|
claude_args: |
|
|
--model claude-opus-5
|
|
--effort high
|
|
--max-turns 200
|
|
--allowedTools "Read,Write,Edit,Glob,Grep,Bash(git:*),Bash(gh:*),Bash(shellcheck:*),Bash(yamllint:*),Bash(docker build:*)"
|
|
env:
|
|
GH_TOKEN: ${{ secrets.AI_PR_TOKEN }}
|
|
|
|
# Consume the approval no matter how the run ended, so a stuck label can
|
|
# never re-fire this workflow, and flag a human if nothing shipped.
|
|
- name: Guard against repeat processing
|
|
if: always() && steps.bundle.outputs.has_plan == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.AI_PR_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh issue edit "$ISSUE" --repo "$REPO" \
|
|
--remove-label ai:approved --remove-label ai:plan-pending >/dev/null 2>&1 || true
|
|
NOW=$(gh issue view "$ISSUE" --repo "$REPO" --json labels --jq '[.labels[].name]')
|
|
if ! echo "$NOW" | jq -e 'index("ai:fixed")!=null' >/dev/null \
|
|
&& ! echo "$NOW" | jq -e 'index("ai:upstream")!=null' >/dev/null; then
|
|
echo "::warning::issue #$ISSUE produced no PR/upstream outcome, flagging for a human"
|
|
gh issue edit "$ISSUE" --repo "$REPO" --add-label ai:needs-human >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# The plan should never touch shared infra, but enforce it on the PR just
|
|
# as the tier-2 sweep does — same script, single source of truth.
|
|
- name: Guard forbidden paths
|
|
if: always() && steps.bundle.outputs.has_plan == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.AI_PR_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Run the guard from the trusted default-branch copy, never the in-tree
|
|
# copy the model could have modified in this same job.
|
|
DEFAULT=$(gh api "repos/${REPO}" --jq '.default_branch' 2>/dev/null || echo master)
|
|
if git fetch --depth=1 origin "$DEFAULT" >/dev/null 2>&1 \
|
|
&& git cat-file -e "FETCH_HEAD:.github/scripts/ai_guard_paths.sh" 2>/dev/null; then
|
|
git show "FETCH_HEAD:.github/scripts/ai_guard_paths.sh" | bash
|
|
else
|
|
echo "::warning::trusted guard copy unavailable, using in-tree copy"
|
|
bash .github/scripts/ai_guard_paths.sh
|
|
fi
|