feat: add GitHub Actions support #75
@@ -0,0 +1,200 @@
|
||||
# This composite action is designed for Gitea Actions runners.
|
||||
|
aweiker marked this conversation as resolved
Outdated
|
||||
# Gitea Actions supports GitHub Actions syntax including $GITHUB_OUTPUT,
|
||||
# actions/cache, and actions/checkout.
|
||||
# Requirements: python3, sha256sum, curl (all present on ubuntu-* runners).
|
||||
name: 'AI Code Review'
|
||||
description: 'Run AI-powered code review on a pull request using review-bot'
|
||||
|
||||
inputs:
|
||||
gitea-url:
|
||||
description: 'Gitea instance URL (defaults to server_url)'
|
||||
required: false
|
||||
default: ''
|
||||
repo:
|
||||
description: 'Repository (owner/name, defaults to current)'
|
||||
required: false
|
||||
default: ''
|
||||
pr-number:
|
||||
description: 'Pull request number (defaults to current PR)'
|
||||
required: false
|
||||
default: ''
|
||||
reviewer-token:
|
||||
description: 'Gitea token for posting the review'
|
||||
required: true
|
||||
reviewer-name:
|
||||
description: 'Display name for the reviewer'
|
||||
required: false
|
||||
default: ''
|
||||
llm-base-url:
|
||||
description: 'OpenAI-compatible LLM API base URL (not required for aicore provider)'
|
||||
required: false
|
||||
default: ''
|
||||
llm-api-key:
|
||||
description: 'LLM API key (not required for aicore provider)'
|
||||
required: false
|
||||
default: ''
|
||||
llm-model:
|
||||
description: 'LLM model name'
|
||||
required: true
|
||||
llm-provider:
|
||||
description: 'LLM API provider: openai, anthropic, or aicore (default openai)'
|
||||
required: false
|
||||
default: 'openai'
|
||||
aicore-client-id:
|
||||
description: 'SAP AI Core client ID (required for aicore provider)'
|
||||
required: false
|
||||
default: ''
|
||||
aicore-client-secret:
|
||||
description: 'SAP AI Core client secret (required for aicore provider)'
|
||||
required: false
|
||||
default: ''
|
||||
aicore-auth-url:
|
||||
description: 'SAP AI Core authentication URL (required for aicore provider)'
|
||||
required: false
|
||||
default: ''
|
||||
aicore-api-url:
|
||||
description: 'SAP AI Core API URL (required for aicore provider)'
|
||||
required: false
|
||||
default: ''
|
||||
aicore-resource-group:
|
||||
description: 'SAP AI Core resource group (default: default)'
|
||||
required: false
|
||||
default: 'default'
|
||||
conventions-file:
|
||||
description: 'Path to conventions file in the repo (e.g. CLAUDE.md)'
|
||||
required: false
|
||||
default: ''
|
||||
patterns-repo:
|
||||
description: 'Comma-separated repos with language patterns (e.g. rodin/elixir-patterns,rodin/phoenix-conventions)'
|
||||
|
aweiker marked this conversation as resolved
gpt-review-bot
commented
[MINOR] Input docs say repo defaults to current, but Determine version sets REPO to "${{ inputs.repo || 'rodin/review-bot' }}". This contradicts the description and may download binaries from the wrong repo by default. Align the default and docs (prefer defaulting to the current repository). **[MINOR]** Input docs say repo defaults to current, but Determine version sets REPO to "${{ inputs.repo || 'rodin/review-bot' }}". This contradicts the description and may download binaries from the wrong repo by default. Align the default and docs (prefer defaulting to the current repository).
|
||||
required: false
|
||||
default: ''
|
||||
patterns-files:
|
||||
description: 'Comma-separated file paths or directories to fetch from patterns repos'
|
||||
required: false
|
||||
default: 'README.md'
|
||||
temperature:
|
||||
description: 'LLM temperature (0 = server default)'
|
||||
required: false
|
||||
default: '0'
|
||||
timeout:
|
||||
description: 'LLM request timeout in seconds (default 300)'
|
||||
required: false
|
||||
default: '300'
|
||||
version:
|
||||
description: 'review-bot version to install (e.g. v0.1.0, defaults to latest)'
|
||||
required: false
|
||||
default: 'latest'
|
||||
dry-run:
|
||||
|
aweiker marked this conversation as resolved
sonnet-review-bot
commented
[NIT] The **[NIT]** The `REPO` default for the binary download is hardcoded to `rodin/review-bot`. When running on GitHub.com (mirrored as `aweiker/ai-core-review-bot`), the download will still attempt to fetch from the Gitea instance at `github.server_url` (which would be `https://github.com`) using the Gitea API path `/api/v1/repos/rodin/review-bot/releases`. This will 404. The action works for Gitea runners but will break if actually invoked as a reusable action on GitHub.com. Consider documenting that `gitea-url` must be explicitly set when using from GitHub.
|
||||
description: 'Print review to stdout instead of posting'
|
||||
required: false
|
||||
default: 'false'
|
||||
update-existing:
|
||||
|
gpt-review-bot
commented
[MINOR] The composite action constructs download and release URLs assuming a Gitea server (e.g., /api/v1 and /releases/download paths). If this action is intended to run on GitHub, it should detect the provider and use GitHub's API for version discovery and asset downloads; otherwise document it as Gitea-only to avoid confusion. **[MINOR]** The composite action constructs download and release URLs assuming a Gitea server (e.g., /api/v1 and /releases/download paths). If this action is intended to run on GitHub, it should detect the provider and use GitHub's API for version discovery and asset downloads; otherwise document it as Gitea-only to avoid confusion.
|
||||
description: 'Delete previous review from same bot after posting new one. Accepts: true/1/yes or false/0/no (default true)'
|
||||
required: false
|
||||
default: 'true'
|
||||
system-prompt-file:
|
||||
description: 'Local file with additional system prompt instructions (e.g. security review focus)'
|
||||
required: false
|
||||
default: ''
|
||||
persona:
|
||||
description: 'Built-in persona name (security, architect, docs)'
|
||||
|
aweiker marked this conversation as resolved
sonnet-review-bot
commented
[NIT] The **[NIT]** The `Determine version` step hardcodes `REPO` as `'rodin/review-bot'` for the download source. On GitHub, this repo may not exist or be accessible, meaning the `Install review-bot` step would fail for GitHub Actions users trying to use this composite action from the public mirror. This is likely intentional (the composite action is primarily for Gitea) but deserves a comment.
|
||||
required: false
|
||||
default: ''
|
||||
persona-file:
|
||||
description: 'Path to custom persona JSON file'
|
||||
|
aweiker marked this conversation as resolved
gpt-review-bot
commented
[MINOR] The cache key for the downloaded binary only uses the version (review-bot-linux-amd64-${{ steps.version.outputs.version }}). If inputs.gitea-url or inputs.repo differ between runs but the tag is the same, a stale or wrong binary could be reused without checksum revalidation. Consider including gitea-url and repo in the cache key or validating checksum on cache hits. **[MINOR]** The cache key for the downloaded binary only uses the version (review-bot-linux-amd64-${{ steps.version.outputs.version }}). If inputs.gitea-url or inputs.repo differ between runs but the tag is the same, a stale or wrong binary could be reused without checksum revalidation. Consider including gitea-url and repo in the cache key or validating checksum on cache hits.
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Determine version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
|
aweiker marked this conversation as resolved
gpt-review-bot
commented
[MAJOR] Determine version step defaults GITEA_URL to github.server_url and queries "${GITEA_URL}/api/v1/repos/${REPO}/releases" (Gitea API). On GitHub (https://github.com) this fails. Make gitea-url required when targeting Gitea releases or add logic to detect GitHub vs Gitea and call the correct API. **[MAJOR]** Determine version step defaults GITEA_URL to github.server_url and queries "${GITEA_URL}/api/v1/repos/${REPO}/releases" (Gitea API). On GitHub (https://github.com) this fails. Make gitea-url required when targeting Gitea releases or add logic to detect GitHub vs Gitea and call the correct API.
|
||||
GITEA_URL="${{ inputs.gitea-url || github.server_url }}"
|
||||
REPO="${{ inputs.repo || 'rodin/review-bot' }}"
|
||||
if [ "${{ inputs.version }}" = "latest" ]; then
|
||||
VERSION=$(curl -sSf "${GITEA_URL}/api/v1/repos/${REPO}/releases?limit=1" \
|
||||
| python3 -c "import sys, json; releases = json.load(sys.stdin); print(releases[0]['tag_name'] if releases else '')")
|
||||
|
security-review-bot marked this conversation as resolved
[MINOR] curl invocations lack explicit timeouts, which could lead to hung jobs and potential denial-of-service on runners if the endpoint stalls. Add --connect-timeout and --max-time to bound network operations. **[MINOR]** curl invocations lack explicit timeouts, which could lead to hung jobs and potential denial-of-service on runners if the endpoint stalls. Add --connect-timeout and --max-time to bound network operations.
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Failed to determine latest version" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
VERSION="${{ inputs.version }}"
|
||||
fi
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache review-bot binary
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ runner.temp }}/review-bot
|
||||
key: review-bot-linux-amd64-${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Install review-bot
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
GITEA_URL="${{ inputs.gitea-url || github.server_url }}"
|
||||
REPO="${{ inputs.repo || 'rodin/review-bot' }}"
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
BINARY="review-bot-linux-amd64"
|
||||
|
||||
curl -sSfL "${GITEA_URL}/${REPO}/releases/download/${VERSION}/${BINARY}" \
|
||||
-o "${{ runner.temp }}/review-bot"
|
||||
curl -sSfL "${GITEA_URL}/${REPO}/releases/download/${VERSION}/checksums.txt" \
|
||||
|
security-review-bot marked this conversation as resolved
[MINOR] The action downloads and executes a binary based on user-provided inputs (gitea-url and repo), and verifies integrity using checksums fetched from the same source. If an untrusted workflow configuration can change these inputs, this allows executing arbitrary code with workflow secrets. Pin the source repository/host, or verify signatures with a trusted key, and avoid allowing untrusted overrides for repo/host. **[MINOR]** The action downloads and executes a binary based on user-provided inputs (gitea-url and repo), and verifies integrity using checksums fetched from the same source. If an untrusted workflow configuration can change these inputs, this allows executing arbitrary code with workflow secrets. Pin the source repository/host, or verify signatures with a trusted key, and avoid allowing untrusted overrides for repo/host.
|
||||
-o "${{ runner.temp }}/checksums.txt"
|
||||
|
||||
|
security-review-bot marked this conversation as resolved
[MINOR] Additional curl downloads of the binary and checksums also lack explicit timeouts. Apply connection and overall timeouts to these requests to prevent job hangs. **[MINOR]** Additional curl downloads of the binary and checksums also lack explicit timeouts. Apply connection and overall timeouts to these requests to prevent job hangs.
|
||||
# Verify SHA-256 checksum
|
||||
cd "${{ runner.temp }}"
|
||||
EXPECTED=$(grep "${BINARY}" checksums.txt | awk '{print $1}')
|
||||
ACTUAL=$(sha256sum review-bot | awk '{print $1}')
|
||||
|
||||
if [ -z "$EXPECTED" ]; then
|
||||
echo "Error: no checksum found for ${BINARY}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$EXPECTED" != "$ACTUAL" ]; then
|
||||
echo "Error: checksum mismatch!" >&2
|
||||
echo " Expected: $EXPECTED" >&2
|
||||
echo " Actual: $ACTUAL" >&2
|
||||
exit 1
|
||||
|
sonnet-review-bot
commented
[MINOR] The **[MINOR]** The `GITHUB_REPOSITORY` env var is set to `${{ inputs.repo || github.repository }}`. When running from GitHub Actions on the mirror repo, `github.repository` would be `aweiker/ai-core-review-bot` (or similar), not `rodin/review-bot`. This means if `inputs.repo` is not explicitly provided, the review-bot would try to post to the wrong Gitea repository. The composite action's `repo` input has no default (`default: ''`), which means the fallback to `github.repository` is always in play when callers don't pass `repo`. This could silently produce incorrect behavior rather than a clear error. Consider either making `repo` required or defaulting it to `rodin/review-bot` to match the documented intent.
|
||||
fi
|
||||
|
||||
chmod +x "${{ runner.temp }}/review-bot"
|
||||
echo "Installed review-bot ${VERSION} (checksum verified)"
|
||||
|
||||
- name: Run review
|
||||
|
gpt-review-bot
commented
[MINOR] The environment variable UPDATE_EXISTING is set from the action input, but the main program (cmd/review-bot/main.go) does not currently read or honor this flag. Either wire this through as a flag/env in the application or remove the input to avoid confusion. **[MINOR]** The environment variable UPDATE_EXISTING is set from the action input, but the main program (cmd/review-bot/main.go) does not currently read or honor this flag. Either wire this through as a flag/env in the application or remove the input to avoid confusion.
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_SERVER_URL: ${{ inputs.gitea-url || github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ inputs.repo || github.repository }}
|
||||
PR_NUMBER: ${{ inputs.pr-number || github.event.pull_request.number }}
|
||||
REVIEWER_TOKEN: ${{ inputs.reviewer-token }}
|
||||
REVIEWER_NAME: ${{ inputs.reviewer-name }}
|
||||
LLM_BASE_URL: ${{ inputs.llm-base-url }}
|
||||
LLM_API_KEY: ${{ inputs.llm-api-key }}
|
||||
LLM_MODEL: ${{ inputs.llm-model }}
|
||||
CONVENTIONS_FILE: ${{ inputs.conventions-file }}
|
||||
PATTERNS_REPO: ${{ inputs.patterns-repo }}
|
||||
PATTERNS_FILES: ${{ inputs.patterns-files }}
|
||||
LLM_TEMPERATURE: ${{ inputs.temperature }}
|
||||
LLM_TIMEOUT: ${{ inputs.timeout }}
|
||||
LLM_PROVIDER: ${{ inputs.llm-provider }}
|
||||
UPDATE_EXISTING: ${{ inputs.update-existing }}
|
||||
SYSTEM_PROMPT_FILE: ${{ inputs.system-prompt-file }}
|
||||
PERSONA: ${{ inputs.persona }}
|
||||
PERSONA_FILE: ${{ inputs.persona-file }}
|
||||
AICORE_CLIENT_ID: ${{ inputs.aicore-client-id }}
|
||||
AICORE_CLIENT_SECRET: ${{ inputs.aicore-client-secret }}
|
||||
AICORE_AUTH_URL: ${{ inputs.aicore-auth-url }}
|
||||
AICORE_API_URL: ${{ inputs.aicore-api-url }}
|
||||
AICORE_RESOURCE_GROUP: ${{ inputs.aicore-resource-group }}
|
||||
run: |
|
||||
ARGS=""
|
||||
if [ "${{ inputs.dry-run }}" = "true" ]; then
|
||||
ARGS="--dry-run"
|
||||
fi
|
||||
${{ runner.temp }}/review-bot $ARGS
|
||||
@@ -0,0 +1,69 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
|
sonnet-review-bot
commented
[NIT] Go version **[NIT]** Go version `1.26` is specified. As of early 2025, Go 1.26 is not yet released (latest stable is 1.23/1.24). If `actions/setup-go` resolves this to a future version that doesn't exist yet, the job would fail. This likely works today if setup-go tolerates forward-looking versions, but it's worth confirming and possibly pinning to a released version. (The Gitea CI presumably already uses this same version and passes, so this may be a non-issue in practice.)
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
|
sonnet-review-bot
commented
[NIT] Go version is specified as **[NIT]** Go version is specified as `1.26`, which does not exist yet (latest stable is 1.24.x as of mid-2025). This may cause `actions/setup-go` to fail or resolve to an unexpected version on GitHub Actions. Recommend pinning to a known stable version like `1.24`.
sonnet-review-bot
commented
[NIT] Go version is set to **[NIT]** Go version is set to `1.26` which does not yet exist (current latest stable is 1.24.x). This may cause `actions/setup-go` to fail on github.com if it cannot resolve the version, whereas Gitea's runner may be more lenient. Consider pinning to an actual released version or using `stable`.
|
||||
with:
|
||||
go-version: '1.26'
|
||||
- run: go test ./...
|
||||
- run: go vet ./...
|
||||
- run: go build -o review-bot ./cmd/review-bot
|
||||
|
||||
# Self-review using native SAP AI Core provider
|
||||
# Models must match SAP AI Core deployments
|
||||
# Available models: gpt-5, anthropic--claude-4.6-sonnet, anthropic--claude-4.6-opus
|
||||
# Removed gpt-4.1, gpt-5-mini, gpt-4.1-mini - not deployed on AI Core
|
||||
review:
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.event_name == 'pull_request'
|
||||
needs: test
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: sonnet
|
||||
token_secret: SONNET_REVIEW_TOKEN
|
||||
model: anthropic--claude-4.6-sonnet
|
||||
- name: gpt
|
||||
token_secret: GPT_REVIEW_TOKEN
|
||||
model: gpt-5
|
||||
- name: security
|
||||
token_secret: SECURITY_REVIEW_TOKEN
|
||||
model: gpt-5
|
||||
patterns_repo: rodin/security-patterns
|
||||
patterns_files: "."
|
||||
system_prompt_file: SECURITY_REVIEW.md
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.26'
|
||||
- run: go build -o review-bot ./cmd/review-bot
|
||||
|
gpt-review-bot
commented
[MAJOR] The review job sets GITHUB_SERVER_URL and GITHUB_REPOSITORY and runs ./review-bot, which will use those to target a Gitea client. On GitHub PRs this points to github.com and will not match Gitea APIs, leading to failures when fetching PRs/diffs and posting reviews. Pass a real Gitea URL if the bot should review on Gitea, or implement a GitHub path. **[MAJOR]** The review job sets GITHUB_SERVER_URL and GITHUB_REPOSITORY and runs ./review-bot, which will use those to target a Gitea client. On GitHub PRs this points to github.com and will not match Gitea APIs, leading to failures when fetching PRs/diffs and posting reviews. Pass a real Gitea URL if the bot should review on Gitea, or implement a GitHub path.
|
||||
- name: Run ${{ matrix.name }} review
|
||||
env:
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
|
gpt-review-bot
commented
[MAJOR] The review job sets GITHUB_SERVER_URL to github.server_url and runs ./review-bot, which initializes a Gitea client using this URL. GitHub’s REST API is not Gitea-compatible, so PR metadata/diff retrieval will fail on GitHub. Either set GITEA_URL to your Gitea instance (if targeting Gitea) or implement/use a GitHub client path. **[MAJOR]** The review job sets GITHUB_SERVER_URL to github.server_url and runs ./review-bot, which initializes a Gitea client using this URL. GitHub’s REST API is not Gitea-compatible, so PR metadata/diff retrieval will fail on GitHub. Either set GITEA_URL to your Gitea instance (if targeting Gitea) or implement/use a GitHub client path.
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
REVIEWER_TOKEN: ${{ secrets[matrix.token_secret] }}
|
||||
|
security-review-bot marked this conversation as resolved
[MAJOR] Secrets are provided to a pull_request job that builds and executes code from the PR (e.g., REVIEWER_TOKEN and multiple AICORE_* secrets). A malicious PR could modify the code to exfiltrate these secrets. Best practice is to avoid using secrets in workflows that run untrusted PR code. **[MAJOR]** Secrets are provided to a pull_request job that builds and executes code from the PR (e.g., REVIEWER_TOKEN and multiple AICORE_* secrets). A malicious PR could modify the code to exfiltrate these secrets. Best practice is to avoid using secrets in workflows that run untrusted PR code.
|
||||
REVIEWER_NAME: ${{ matrix.name }}
|
||||
LLM_PROVIDER: aicore
|
||||
LLM_MODEL: ${{ matrix.model }}
|
||||
AICORE_CLIENT_ID: ${{ secrets.AICORE_CLIENT_ID }}
|
||||
AICORE_CLIENT_SECRET: ${{ secrets.AICORE_CLIENT_SECRET }}
|
||||
AICORE_AUTH_URL: ${{ secrets.AICORE_AUTH_URL }}
|
||||
AICORE_API_URL: ${{ secrets.AICORE_API_URL }}
|
||||
AICORE_RESOURCE_GROUP: ${{ secrets.AICORE_RESOURCE_GROUP }}
|
||||
CONVENTIONS_FILE: "CONVENTIONS.md"
|
||||
PATTERNS_REPO: ${{ matrix.patterns_repo || 'rodin/go-patterns' }}
|
||||
PATTERNS_FILES: ${{ matrix.patterns_files || 'README.md,patterns/' }}
|
||||
LLM_TIMEOUT: "600"
|
||||
SYSTEM_PROMPT_FILE: ${{ matrix.system_prompt_file }}
|
||||
run: ./review-bot
|
||||
@@ -0,0 +1,38 @@
|
||||
name: PR Ready Gate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize]
|
||||
|
||||
jobs:
|
||||
clear-labels:
|
||||
runs-on: ubuntu-24.04
|
||||
# Always run - curl commands are safe if labels don't exist
|
||||
steps:
|
||||
- name: Remove ready and self-reviewed labels, reassign to author
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RODIN_TOKEN }}
|
||||
run: |
|
||||
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||
AUTHOR=${{ github.event.pull_request.user.login }}
|
||||
READY_LABEL_ID=38
|
||||
SELF_REVIEWED_LABEL_ID=37
|
||||
|
||||
|
gpt-review-bot
commented
[MINOR] Workflow targets Gitea API using GitHub PR number and repository. PR numbers generally won’t match across GitHub and Gitea, so operations will likely be no-ops. Consider gating this workflow to only run where the PR exists (or map PR numbers), or explicitly document that it manipulates the Gitea mirror. **[MINOR]** Workflow targets Gitea API using GitHub PR number and repository. PR numbers generally won’t match across GitHub and Gitea, so operations will likely be no-ops. Consider gating this workflow to only run where the PR exists (or map PR numbers), or explicitly document that it manipulates the Gitea mirror.
|
||||
# Remove ready label if present
|
||||
curl -sS -X DELETE \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
|
sonnet-review-bot
commented
[MINOR] The Gitea API URL is hardcoded as **[MINOR]** The Gitea API URL is hardcoded as `https://gitea.weiker.me` rather than using `github.server_url`. This workflow will fail silently (curl will hit the Gitea instance) when running on GitHub.com, since `secrets.RODIN_TOKEN` won't be a valid GitHub token and the API path (`/api/v1/`) is Gitea-specific. If this workflow is intended to be a no-op on GitHub, that should be explicitly documented or the job should be conditioned on the server URL.
sonnet-review-bot
commented
[MINOR] Label IDs 38 and 37 are hardcoded without explanation. If the Gitea instance label IDs ever change (e.g., after a data migration or re-creation), this silently does nothing. The comment explains WHY we use Gitea but not WHY these specific numeric IDs are correct. A brief comment naming which labels these IDs correspond to (e.g., **[MINOR]** Label IDs 38 and 37 are hardcoded without explanation. If the Gitea instance label IDs ever change (e.g., after a data migration or re-creation), this silently does nothing. The comment explains WHY we use Gitea but not WHY these specific numeric IDs are correct. A brief comment naming which labels these IDs correspond to (e.g., `# 38 = ready, 37 = self-reviewed`) would improve maintainability.
gpt-review-bot
commented
[MINOR] This job calls a hardcoded Gitea instance (https://gitea.weiker.me) using GitHub event data. On GitHub PRs, the PR number and repo may not exist on that instance, which could fail the job. Consider scoping this job to your Gitea environment or guarding it with conditions so it doesn't run (or fail) on GitHub. **[MINOR]** This job calls a hardcoded Gitea instance (https://gitea.weiker.me) using GitHub event data. On GitHub PRs, the PR number and repo may not exist on that instance, which could fail the job. Consider scoping this job to your Gitea environment or guarding it with conditions so it doesn't run (or fail) on GitHub.
|
||||
"https://gitea.weiker.me/api/v1/repos/${{ github.repository }}/issues/${PR_NUMBER}/labels/${READY_LABEL_ID}" || true
|
||||
|
sonnet-review-bot
commented
[MINOR] The label IDs (READY_LABEL_ID=38, SELF_REVIEWED_LABEL_ID=37) and the hardcoded Gitea URL ( **[MINOR]** The label IDs (READY_LABEL_ID=38, SELF_REVIEWED_LABEL_ID=37) and the hardcoded Gitea URL (`https://gitea.weiker.me`) are Gitea-specific and will silently no-op (via `|| true`) on GitHub Actions. This is acceptable for now since the workflow is a Gitea-only feature, but it means the workflow is essentially a dead stub on GitHub. A comment explaining this would reduce confusion.
|
||||
|
||||
# Remove self-reviewed label if present
|
||||
curl -sS -X DELETE \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://gitea.weiker.me/api/v1/repos/${{ github.repository }}/issues/${PR_NUMBER}/labels/${SELF_REVIEWED_LABEL_ID}" || true
|
||||
|
||||
# Reassign to author
|
||||
curl -sS -X PATCH \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"assignees\": [\"${AUTHOR}\"]}" \
|
||||
"https://gitea.weiker.me/api/v1/repos/${{ github.repository }}/pulls/${PR_NUMBER}"
|
||||
|
||||
echo "Cleared ready/self-reviewed labels and reassigned PR #${PR_NUMBER} to ${AUTHOR}"
|
||||
@@ -0,0 +1,97 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.26'
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
go vet ./...
|
||||
go test ./...
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
VERSION=${GITHUB_REF_NAME}
|
||||
mkdir -p dist
|
||||
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.version=${VERSION}" -o dist/review-bot-linux-amd64 ./cmd/review-bot
|
||||
GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X main.version=${VERSION}" -o dist/review-bot-linux-arm64 ./cmd/review-bot
|
||||
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.version=${VERSION}" -o dist/review-bot-darwin-amd64 ./cmd/review-bot
|
||||
GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w -X main.version=${VERSION}" -o dist/review-bot-darwin-arm64 ./cmd/review-bot
|
||||
|
||||
cd dist && sha256sum * > checksums.txt
|
||||
|
||||
- name: Create release and upload assets
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
VERSION=${GITHUB_REF_NAME}
|
||||
GITEA_URL="${{ github.server_url }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
# Create release (or find existing one for this tag)
|
||||
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
|
security-review-bot marked this conversation as resolved
[MINOR] Multiple curl API calls (creating/fetching releases and listing/uploading/deleting assets) do not set explicit timeouts, increasing risk of runner hangs. Add --connect-timeout and --max-time to these requests (and subsequent ones at lines ~53, ~79, ~86, ~91) to mitigate DoS via stalled endpoints. **[MINOR]** Multiple curl API calls (creating/fetching releases and listing/uploading/deleting assets) do not set explicit timeouts, increasing risk of runner hangs. Add --connect-timeout and --max-time to these requests (and subsequent ones at lines ~53, ~79, ~86, ~91) to mitigate DoS via stalled endpoints.
|
||||
-H "Content-Type: application/json" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/releases" \
|
||||
-d "{\"tag_name\": \"${VERSION}\", \"name\": \"${VERSION}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}")
|
||||
|
gpt-review-bot
commented
[MAJOR] Release step uses GITEA_URL="${{ github.server_url }}" and then calls "${GITEA_URL}/api/v1/repos/.../releases". On GitHub, server_url is https://github.com (GitHub releases live at api.github.com and use different endpoints). This will not work on GitHub. Use GitHub's REST API (or an action like softprops/action-gh-release) when running on GitHub, and keep the Gitea API for Gitea. **[MAJOR]** Release step uses GITEA_URL="${{ github.server_url }}" and then calls "${GITEA_URL}/api/v1/repos/.../releases". On GitHub, server_url is https://github.com (GitHub releases live at api.github.com and use different endpoints). This will not work on GitHub. Use GitHub's REST API (or an action like softprops/action-gh-release) when running on GitHub, and keep the Gitea API for Gitea.
|
||||
|
||||
if [ "$HTTP_CODE" = "409" ]; then
|
||||
echo "Release for ${VERSION} already exists, fetching existing..."
|
||||
curl -sSf -o /tmp/release_response.json \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/releases/tags/${VERSION}"
|
||||
|
aweiker marked this conversation as resolved
gpt-review-bot
commented
[MAJOR] Release step uses GITEA_URL="${{ github.server_url }}" and calls /api/v1 (Gitea API). On GitHub this endpoint is invalid; release creation and asset upload will fail. If releases are intended on GitHub, switch to GitHub’s Releases API (api.github.com/repos/:owner/:repo/releases) or use actions like actions/create-release. If releases are intended on Gitea, set GITEA_URL explicitly to the Gitea server. **[MAJOR]** Release step uses GITEA_URL="${{ github.server_url }}" and calls /api/v1 (Gitea API). On GitHub this endpoint is invalid; release creation and asset upload will fail. If releases are intended on GitHub, switch to GitHub’s Releases API (api.github.com/repos/:owner/:repo/releases) or use actions like actions/create-release. If releases are intended on Gitea, set GITEA_URL explicitly to the Gitea server.
|
||||
elif [ "$HTTP_CODE" != "201" ]; then
|
||||
echo "Failed to create release (HTTP ${HTTP_CODE})" >&2
|
||||
cat /tmp/release_response.json >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse release ID (python3 available on ubuntu-24.04 runners)
|
||||
RELEASE_ID=$(python3 -c "import json; print(json.load(open('/tmp/release_response.json'))['id'])")
|
||||
|
||||
if [ -z "$RELEASE_ID" ]; then
|
||||
echo "Failed to parse release ID" >&2
|
||||
cat /tmp/release_response.json >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Release ID: ${RELEASE_ID}"
|
||||
|
||||
# Upload each asset (idempotent: delete existing asset with same name first)
|
||||
for file in dist/*; do
|
||||
filename=$(basename "$file")
|
||||
echo "Uploading ${filename}..."
|
||||
|
||||
# Check if asset already exists and delete it
|
||||
EXISTING_ID=$(export ASSET_NAME="${filename}"; curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets" \
|
||||
| python3 -c "import json,sys,os; name=os.environ['ASSET_NAME']; assets=json.load(sys.stdin); print(next((str(a['id']) for a in assets if a['name']==name),''))" 2>/dev/null)
|
||||
|
||||
if [ -n "$EXISTING_ID" ]; then
|
||||
|
aweiker marked this conversation as resolved
gpt-review-bot
commented
[NIT] The upload script uses jq for URL-encoding without ensuring it’s present. ubuntu-24.04 runners typically include jq, but adding an explicit install or a pure-shell URL-encode avoids environment dependence. **[NIT]** The upload script uses jq for URL-encoding without ensuring it’s present. ubuntu-24.04 runners typically include jq, but adding an explicit install or a pure-shell URL-encode avoids environment dependence.
|
||||
echo " Asset ${filename} already exists (id=${EXISTING_ID}), deleting..."
|
||||
curl -sSf -X DELETE \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
|
aweiker marked this conversation as resolved
gpt-review-bot
commented
[MINOR] The release job uses **[MINOR]** The release job uses `jq` to URL-encode asset names but doesn't ensure `jq` is installed. While Ubuntu runners often include jq, it's safer to either install it explicitly or replace this with a Python-based encoding to avoid environment drift.
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets/${EXISTING_ID}"
|
||||
fi
|
||||
|
||||
curl -sSf -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=$(printf '%s' "${filename}" | jq -sRr @uri)" \
|
||||
--data-binary "@${file}"
|
||||
done
|
||||
|
||||
echo "Release ${VERSION} created with assets"
|
||||
@@ -54,8 +54,8 @@ func main() {
|
||||
logFormat := flag.String("log-format", envOrDefault("LOG_FORMAT", "text"), "Log output format: text or json")
|
||||
verbosity := flag.String("verbosity", envOrDefault("LOG_VERBOSITY", "info"), "Log verbosity: debug, info, warn, error")
|
||||
|
aweiker marked this conversation as resolved
gpt-review-bot
commented
[NIT] The fallback to GITHUB_SERVER_URL/GITHUB_REPOSITORY is fine, but note that the client remains Gitea-specific. Consider clarifying in help text that GitHub support requires pointing to a Gitea-compatible API or implementing a GitHub client path. **[NIT]** The fallback to GITHUB_SERVER_URL/GITHUB_REPOSITORY is fine, but note that the client remains Gitea-specific. Consider clarifying in help text that GitHub support requires pointing to a Gitea-compatible API or implementing a GitHub client path.
|
||||
// CLI flags
|
||||
|
gpt-review-bot
commented
[MAJOR] Defaults now fall back to GITHUB_SERVER_URL/GITHUB_REPOSITORY for --gitea-url/--repo, but the bot constructs a Gitea client and calls Gitea v1 APIs. On GitHub, GITHUB_SERVER_URL is https://github.com and lacks /api/v1; this will cause API calls to fail. Either keep requiring explicit GITEA_URL/GITEA_REPO when targeting Gitea, or add a GitHub client and switch on provider. **[MAJOR]** Defaults now fall back to GITHUB_SERVER_URL/GITHUB_REPOSITORY for --gitea-url/--repo, but the bot constructs a Gitea client and calls Gitea v1 APIs. On GitHub, GITHUB_SERVER_URL is https://github.com and lacks /api/v1; this will cause API calls to fail. Either keep requiring explicit GITEA_URL/GITEA_REPO when targeting Gitea, or add a GitHub client and switch on provider.
|
||||
giteaURL := flag.String("gitea-url", envOrDefault("GITEA_URL", ""), "Gitea instance URL")
|
||||
repo := flag.String("repo", envOrDefault("GITEA_REPO", ""), "Repository (owner/name)")
|
||||
giteaURL := flag.String("gitea-url", envOrDefault("GITEA_URL", envOrDefault("GITHUB_SERVER_URL", "")), "Gitea instance URL")
|
||||
repo := flag.String("repo", envOrDefault("GITEA_REPO", envOrDefault("GITHUB_REPOSITORY", "")), "Repository (owner/name)")
|
||||
prNum := flag.String("pr", envOrDefault("PR_NUMBER", ""), "Pull request number")
|
||||
reviewerName := flag.String("reviewer-name", envOrDefault("REVIEWER_NAME", ""), "Reviewer display name")
|
||||
reviewerToken := flag.String("reviewer-token", envOrDefault("REVIEWER_TOKEN", ""), "Gitea token for posting review")
|
||||
|
||||
[MINOR] The action comment says 'This composite action is designed for Gitea Actions runners' but this file is now in
.github/for GitHub Actions. The comment is misleading for GitHub Actions users who encounter this file. Consider updating the comment to reflect dual-platform support.[MINOR] The comment at the top says 'This composite action is designed for Gitea Actions runners' but the file is now in
.github/and intended to run on GitHub Actions as well. The comment is misleading for GitHub Actions users who discover this action.