Skip to content

Fix lint errors

Fix lint errors #76

Workflow file for this run

name: AI Auto-Fix
on:
pull_request_review:
types: [submitted]
issue_comment:
types: [created]
# Only one auto-fix at a time per PR — don't cancel in progress (let the fix finish)
concurrency:
group: pr-fix-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: false
jobs:
auto-fix:
# Trigger on: (1) changes_requested review, or (2) /fix comment on a PR from repo members.
# Keep the member guard only on /fix comments: review-triggered auto-fix must not be skipped
# before the runner starts when the review comes from the dedicated review account.
if: >-
(github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested') ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '/fix') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
runs-on: [self-hosted, builder]
steps:
- name: Get PR info for comment trigger
id: pr-info
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
COMMENT_USER: ${{ github.event.comment.user.login }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_NUMBER_EVENT: ${{ github.event.pull_request.number }}
REVIEW_USER: ${{ github.event.review.user.login }}
run: |
if [ "$EVENT_NAME" = "issue_comment" ]; then
PR_DATA=$(gh pr view "$ISSUE_NUMBER" --json headRefName -q '.headRefName')
echo "branch=$PR_DATA" >> $GITHUB_OUTPUT
echo "pr_number=$ISSUE_NUMBER" >> $GITHUB_OUTPUT
echo "reviewer=$COMMENT_USER" >> $GITHUB_OUTPUT
else
echo "branch=$PR_HEAD_REF" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER_EVENT" >> $GITHUB_OUTPUT
echo "reviewer=$REVIEW_USER" >> $GITHUB_OUTPUT
fi
- uses: actions/checkout@v4
with:
ref: ${{ steps.pr-info.outputs.branch }}
fetch-depth: 0
clean: false
token: ${{ secrets.PAT_TOKEN }}
- name: Rebase on dev if behind
run: |
git fetch origin dev
if ! git merge-base --is-ancestor origin/dev HEAD; then
echo "Branch is behind dev, rebasing..."
if git rebase origin/dev; then
git push --force-with-lease
echo "Rebase successful"
else
git rebase --abort
gh pr comment ${{ steps.pr-info.outputs.pr_number }} \
--body "Merge conflict with dev requires manual resolution."
exit 1
fi
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Security: the PR tree is attacker-controlled. Remove any Claude/MCP config
# it carries before running `claude` — these files would otherwise execute
# SessionStart hooks / spawn MCP servers as the runner user. Placed AFTER
# rebase so deleting a PR-tracked file does not dirty the tree and break it.
- name: Strip attacker-controlled Claude/MCP config
run: |
rm -f .claude/settings.json .claude/settings.local.json .mcp.json .claude/.mcp.json
- name: Check iteration limit
id: check
run: |
PR_NUMBER="${{ steps.pr-info.outputs.pr_number }}"
# Detect bot vs human by review content marker (no hardcoded usernames)
# Bot reviews contain "## AI Review Summary", human reviews don't
IS_HUMAN="false"
if [ "${{ github.event_name }}" = "issue_comment" ]; then
IS_HUMAN="true"
else
REVIEW_BODY=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/reviews" \
--jq "[.[] | select(.id == ${{ github.event.review.id }})] | .[0].body // \"\"")
if ! echo "$REVIEW_BODY" | grep -q "## AI Review Summary"; then
IS_HUMAN="true"
fi
fi
echo "is_human=$IS_HUMAN" >> $GITHUB_OUTPUT
# Count auto-fix iterations from monitoring DB
COUNT=$(sqlite3 /data/monitoring/monitoring.db \
"SELECT COUNT(*) FROM agent_runs WHERE agent='pr-fixer' AND run_id LIKE '%fix%' AND issue_number=$PR_NUMBER" 2>/dev/null || echo "0")
# Fallback: count fix commits on the branch
if [ "$COUNT" = "0" ]; then
COUNT=$(git log --oneline --grep="auto-fix iteration" | wc -l | tr -d ' ')
fi
echo "count=$COUNT" >> $GITHUB_OUTPUT
if [ "$IS_HUMAN" = "false" ] && [ "$COUNT" -ge "3" ]; then
gh pr comment "$PR_NUMBER" \
--body "Auto-fix limit (3) reached for automated reviews. Agent provided explanations for unresolved issues above."
echo "blocked=true" >> $GITHUB_OUTPUT
else
echo "blocked=false" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies and generate Prisma
if: steps.check.outputs.blocked != 'true'
timeout-minutes: 30
run: |
rm -rf .next
if [ -f node_modules/.yarn-integrity ]; then
echo "node_modules cached, skipping install"
else
yarn install --frozen-lockfile || yarn install
fi
npx prisma generate 2>/dev/null || true
npx prisma generate --schema=prisma/events/schema.prisma 2>/dev/null || true
- name: Fix
if: steps.check.outputs.blocked != 'true'
run: |
PR_NUMBER="${{ steps.pr-info.outputs.pr_number }}"
DB="/data/monitoring/monitoring.db"
ITERATION=$(( ${{ steps.check.outputs.count }} + 1 ))
RUN_ID="${GITHUB_RUN_ID}-fix-${ITERATION}"
START_MS=$(date +%s%3N)
# Log start
sqlite3 "$DB" "INSERT INTO agent_runs
(run_id, agent, role, trigger, issue_number, started_at)
VALUES ('$RUN_ID', 'pr-fixer', 'builder', 'review', $PR_NUMBER, datetime('now'))" 2>/dev/null || true
# Collect PR history for context
FIX_COMMITS=$(git log --oneline --grep="auto-fix iteration" \
origin/dev..HEAD 2>/dev/null || echo "No previous fix commits")
PR_COMMENTS=$(gh pr view "$PR_NUMBER" \
--json comments \
-q '[.comments[]] | map(.body) | join("\n---\n")')
# Separate human and bot reviews by content marker (no hardcoded usernames)
HUMAN_COMMENTS=$(gh pr view "$PR_NUMBER" \
--json reviews \
-q '[.reviews[] | select(.body | contains("## AI Review Summary") | not)] | map(.body) | join("\n---\n")')
BOT_COMMENTS=$(gh pr view "$PR_NUMBER" \
--json reviews \
-q '[.reviews[] | select(.body | contains("## AI Review Summary"))] | map(.body) | join("\n---\n")')
# Build prompt file (avoids shell injection from review text containing quotes)
PROMPT_FILE="/tmp/fix-prompt-${ITERATION}.txt"
cat > "$PROMPT_FILE" <<FIXPROMPTEOF
## TASK: Fix review feedback on PR #${PR_NUMBER}
ITERATION: ${ITERATION} of 3
## PR FIX HISTORY (your previous attempts):
${FIX_COMMITS}
## PR COMMENTS (conversation history):
${PR_COMMENTS}
FIXPROMPTEOF
if [ "${{ github.event_name }}" = "issue_comment" ]; then
printf '\n## FIX COMMAND (from PR author — HIGHEST PRIORITY):\n%s\n' "$COMMENT_BODY" >> "$PROMPT_FILE"
fi
printf '\n## HUMAN REVIEW (HIGHEST PRIORITY — always follow):\n%s\n' "$HUMAN_COMMENTS" >> "$PROMPT_FILE"
printf '\n## AUTOMATED REVIEW (lower priority — follow only if no conflict with human):\n%s\n' "$BOT_COMMENTS" >> "$PROMPT_FILE"
cat >> "$PROMPT_FILE" <<FIXRULESEOF
BEFORE fixing anything:
1. Read the PR FIX HISTORY above. These are your previous fix commits.
2. Read ALL review comments (human + bot), not just the latest.
3. Identify which review comments are NEW (first time) vs REPEATED
(appeared in a previous review iteration).
4. For REPEATED comments: your previous approach did not work.
Analyze WHY by reading your fix commit and the repeated comment.
Try a DIFFERENT approach this time.
5. For NEW comments: fix normally.
CONFLICT RULE: If human and automated reviews contradict each other,
ALWAYS follow the human review. Human is the final authority.
Before fixing any reported issue, verify it is actually a real problem
by reading the relevant files. If a review comment is wrong
(e.g. claims an import path is invalid but tsconfig.json confirms it exists),
do NOT make that change — skip it.
If this is iteration 3 (final) and you cannot resolve a comment:
- Write a PR comment for EACH unresolved issue:
"Could not fix: [issue summary]
Reason: [why your attempts failed]
Alternative: [proposed different approach]"
- Do NOT force a fix you are not confident about.
Rules: only fix what was requested and verified as real. Follow CLAUDE.md.
Commit message: fix: address review feedback (auto-fix iteration ${ITERATION})
Do NOT add any Co-Authored-By trailer to commit messages.
FIXRULESEOF
# Load fullstack.md as agent persona (provides Skills, Teams, GitNexus workflow).
# Read from the trusted origin/dev blob, NOT the attacker-controlled PR tree —
# a PR editing this file would otherwise inject into --append-system-prompt.
AGENT_PERSONA=$(git show origin/dev:.claude/agents/fullstack.md 2>/dev/null || echo "")
claude -p "$(cat "$PROMPT_FILE")" --model claude-opus-4-6 \
--append-system-prompt "$AGENT_PERSONA" \
--dangerously-skip-permissions \
--output-format text
EXIT_CODE=$?
END_MS=$(date +%s%3N)
DURATION=$((END_MS - START_MS))
STATUS=$([ $EXIT_CODE -eq 0 ] && echo "success" || echo "error")
# Log finish
sqlite3 "$DB" "UPDATE agent_runs SET
finished_at = datetime('now'), status = '$STATUS',
exit_code = $EXIT_CODE, duration_ms = $DURATION
WHERE run_id = '$RUN_ID'" 2>/dev/null || true
sqlite3 "$DB" "INSERT INTO workflow_events
(run_id, workflow, event, issue_number, details)
VALUES ('$RUN_ID', 'pr-fix', '$STATUS',
$PR_NUMBER,
'{\"duration_ms\": $DURATION, \"iteration\": $ITERATION, \"is_human\": \"${{ steps.check.outputs.is_human }}\"}')" 2>/dev/null || true
if [ $EXIT_CODE -ne 0 ]; then
echo "::error::PR auto-fix failed (iteration $ITERATION)"
exit 1
fi
git push
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_BODY: ${{ github.event.comment.body }}