feat(dashboard-api): route logs through read-only service endpoint #240
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude Code Review | |
| # Consolidated AI code review workflow (replaces phases 1, 2, 3) | |
| # | |
| # Jobs run conditionally: | |
| # - basic-review: every PR open/sync (~$1.50) | |
| # - detect-high-stakes + review-summary: flags sensitive files for extra human attention | |
| # - security-check + claude-fix: only when 'ai-fix' label applied (~$5-10, opt-in) | |
| # | |
| # Note: Draft PRs created by claude-fix use GITHUB_TOKEN, so CI won't | |
| # run automatically on them. A maintainer must interact to trigger CI. | |
| on: | |
| pull_request: | |
| types: [opened, ready_for_review, synchronize, labeled] | |
| branches-ignore: | |
| - 'ai/**' | |
| - 'scanner/**' | |
| - 'issue-fix/**' | |
| - 'nightly/**' | |
| issue_comment: | |
| types: [created] | |
| concurrency: | |
| group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| jobs: | |
| # ─── Phase 1: Basic comment-only review ─────────────────────────── | |
| basic-review: | |
| name: Basic Review | |
| if: | | |
| github.event.action != 'labeled' && | |
| github.actor != 'claude[bot]' && | |
| github.actor != 'dependabot[bot]' && | |
| github.actor != 'github-actions[bot]' && | |
| ( | |
| github.event_name == 'pull_request' || | |
| (github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| contains(github.event.comment.body, '@claude-review')) | |
| ) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Check if fork PR | |
| id: fork_check | |
| env: | |
| HAS_API_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }} | |
| run: | | |
| IS_FORK="false" | |
| # For pull_request events, check head repo | |
| if [ "${{ github.event_name }}" == "pull_request" ]; then | |
| if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then | |
| IS_FORK="true" | |
| fi | |
| fi | |
| # For issue_comment events, API key absence signals fork PR | |
| if [ "${{ github.event_name }}" == "issue_comment" ] && [ "$HAS_API_KEY" != "true" ]; then | |
| IS_FORK="true" | |
| fi | |
| echo "is_fork=$IS_FORK" >> $GITHUB_OUTPUT | |
| if [ "$IS_FORK" == "true" ]; then | |
| echo "::notice::Fork PR detected — skipping AI review (no API key available)" | |
| fi | |
| - name: Checkout code | |
| if: steps.fork_check.outputs.is_fork != 'true' | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check PR size (cost control) | |
| if: steps.fork_check.outputs.is_fork != 'true' | |
| id: pr_size | |
| run: | | |
| BASE_REF="${{ github.base_ref || github.event.repository.default_branch || 'main' }}" | |
| FILES_CHANGED=$(git diff --name-only origin/${BASE_REF}...HEAD | wc -l) | |
| LINES_CHANGED=$(git diff --stat origin/${BASE_REF}...HEAD | tail -1 | awk '{print $4+$6}') | |
| echo "files_changed=$FILES_CHANGED" >> $GITHUB_OUTPUT | |
| echo "lines_changed=$LINES_CHANGED" >> $GITHUB_OUTPUT | |
| if [ "$LINES_CHANGED" -gt 1000 ]; then | |
| echo "skip_review=true" >> $GITHUB_OUTPUT | |
| echo "::warning::PR too large for AI review (>1000 lines). Add 'force-review' label to override." | |
| else | |
| echo "skip_review=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Skip large PR notice | |
| if: | | |
| steps.fork_check.outputs.is_fork != 'true' && | |
| steps.pr_size.outputs.skip_review == 'true' && | |
| !contains(github.event.pull_request.labels.*.name, 'force-review') | |
| run: | | |
| echo "::notice::Skipping review for large PR. Add 'force-review' label to override." | |
| exit 0 | |
| - name: Run Claude Code Review | |
| if: | | |
| steps.fork_check.outputs.is_fork != 'true' && | |
| (steps.pr_size.outputs.skip_review == 'false' || contains(github.event.pull_request.labels.*.name, 'force-review')) | |
| uses: anthropics/claude-code-action@58dbe8ed6879f0d3b02ac295b20d5fdfe7733e0c # v1 | |
| with: | |
| claude_args: | | |
| code-review --comment \ | |
| --model claude-opus-4-5-20251101 \ | |
| --max-turns 20 \ | |
| --allowedTools "Bash(git diff *),Bash(git log *),Bash(git blame *),Read" | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| - name: Review metrics | |
| if: always() | |
| env: | |
| IS_FORK: ${{ steps.fork_check.outputs.is_fork }} | |
| FILES_CHANGED: ${{ steps.pr_size.outputs.files_changed }} | |
| LINES_CHANGED: ${{ steps.pr_size.outputs.lines_changed }} | |
| run: | | |
| echo "### Review Metrics" >> $GITHUB_STEP_SUMMARY | |
| if [ "$IS_FORK" == "true" ]; then | |
| echo "- Skipped: fork PR (no API key)" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- Files changed: ${FILES_CHANGED:-0}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Lines changed: ${LINES_CHANGED:-0}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Estimated cost: ~\$1.50" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # ─── Phase 2: Sensitive file detection ──────────────────────────── | |
| detect-high-stakes: | |
| name: Detect High-Stakes Changes | |
| if: github.event_name == 'pull_request' && github.event.action != 'labeled' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| is_high_stakes: ${{ steps.check.outputs.is_high_stakes }} | |
| sensitive_files: ${{ steps.check.outputs.sensitive_files }} | |
| reason: ${{ steps.check.outputs.reason }} | |
| is_fork: ${{ steps.fork-check.outputs.is_fork }} | |
| steps: | |
| - name: Check if fork PR | |
| id: fork-check | |
| run: | | |
| if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then | |
| echo "is_fork=true" >> $GITHUB_OUTPUT | |
| echo "::notice::Fork PR detected — some review features will be skipped" | |
| else | |
| echo "is_fork=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for high-stakes changes | |
| id: check | |
| run: | | |
| HIGH_STAKES_PATTERNS=( | |
| "dream-server/installers/" | |
| "dream-server/dream-cli" | |
| "dream-server/config/" | |
| "dream-server/extensions/services/dashboard-api/crates/dashboard-api/src/middleware.rs" | |
| ".github/workflows/" | |
| ".env" | |
| "docker-compose" | |
| ) | |
| CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref || github.event.repository.default_branch || 'main' }}...HEAD) | |
| IS_HIGH_STAKES="false" | |
| SENSITIVE_FILES="" | |
| REASON="" | |
| for pattern in "${HIGH_STAKES_PATTERNS[@]}"; do | |
| if echo "$CHANGED_FILES" | grep -i "$pattern" > /dev/null; then | |
| IS_HIGH_STAKES="true" | |
| SENSITIVE_FILES=$(echo "$CHANGED_FILES" | grep -i "$pattern" | head -5) | |
| REASON="Security-sensitive files detected: $pattern" | |
| break | |
| fi | |
| done | |
| if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ai-consensus') }}" == "true" ]]; then | |
| IS_HIGH_STAKES="true" | |
| REASON="Manual review escalation requested via label" | |
| fi | |
| echo "is_high_stakes=$IS_HIGH_STAKES" >> $GITHUB_OUTPUT | |
| echo "sensitive_files<<EOF" >> $GITHUB_OUTPUT | |
| echo "$SENSITIVE_FILES" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| echo "reason=$REASON" >> $GITHUB_OUTPUT | |
| if [ "$IS_HIGH_STAKES" == "true" ]; then | |
| echo "::notice::High-stakes changes detected — flagging for extra review" | |
| fi | |
| review-summary: | |
| name: Review Summary | |
| needs: [detect-high-stakes] | |
| if: | | |
| always() && | |
| needs.detect-high-stakes.outputs.is_fork != 'true' && | |
| needs.detect-high-stakes.outputs.is_high_stakes == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Post high-stakes notice | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const reason = `${{ needs.detect-high-stakes.outputs.reason }}`; | |
| const sensitiveFiles = `${{ needs.detect-high-stakes.outputs.sensitive_files }}`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.pull_request.number, | |
| body: `## Sensitive Files Detected | |
| **Trigger**: ${reason} | |
| **Files flagged**: | |
| \`\`\` | |
| ${sensitiveFiles} | |
| \`\`\` | |
| Extra human review is recommended for this PR. | |
| --- | |
| Claude Code Review | Sensitive File Detection | ~$1.50` | |
| }); | |
| # ─── Phase 3: Auto-fix (opt-in via 'ai-fix' label) ─────────────── | |
| security-check: | |
| name: Pre-flight Security Check | |
| if: | | |
| github.event.action == 'labeled' && | |
| contains(github.event.pull_request.labels.*.name, 'ai-fix') && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| outputs: | |
| safe_to_proceed: ${{ steps.check.outputs.safe }} | |
| blocked_reason: ${{ steps.check.outputs.reason }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Security validation | |
| id: check | |
| run: | | |
| SAFE="true" | |
| REASON="" | |
| CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref || github.event.repository.default_branch || 'main' }}...HEAD) | |
| BLOCKED_PATTERNS=( | |
| ".github/workflows/" | |
| ".github/actions/" | |
| "dream-server/installers/" | |
| "dream-server/dream-cli" | |
| "dream-server/config/" | |
| "\.env" | |
| "\.env\." | |
| "\.key$" | |
| "\.pem$" | |
| "credentials" | |
| ) | |
| for pattern in "${BLOCKED_PATTERNS[@]}"; do | |
| if echo "$CHANGED_FILES" | grep -qE "$pattern" 2>/dev/null; then | |
| SAFE="false" | |
| REASON="Modifications to protected files detected: $pattern. AI patch generation not allowed." | |
| break | |
| fi | |
| done | |
| if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then | |
| SAFE="false" | |
| REASON="PR from fork — AI patch generation disabled for security" | |
| fi | |
| echo "safe=$SAFE" >> $GITHUB_OUTPUT | |
| echo "reason=$REASON" >> $GITHUB_OUTPUT | |
| if [ "$SAFE" == "false" ]; then | |
| echo "::error::$REASON" | |
| fi | |
| claude-fix: | |
| name: Claude Code Review + Patch Generation | |
| needs: security-check | |
| if: | | |
| needs.security-check.outputs.safe_to_proceed == 'true' && | |
| github.actor != 'claude[bot]' && | |
| github.actor != 'dependabot[bot]' && | |
| github.actor != 'github-actions[bot]' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run Claude Code Review with write permissions | |
| id: review | |
| uses: anthropics/claude-code-action@58dbe8ed6879f0d3b02ac295b20d5fdfe7733e0c # v1 | |
| with: | |
| claude_args: | | |
| code-review \ | |
| --model claude-opus-4-5-20251101 \ | |
| --max-turns 25 \ | |
| --allowedTools "Bash(git diff *),Bash(git log *),Read,Edit,Write" | |
| use_commit_signing: true | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| - name: Parse review results | |
| id: parse | |
| run: | | |
| if git diff --quiet; then | |
| echo "has_suggestions=false" >> $GITHUB_OUTPUT | |
| echo "::notice::No code changes suggested by AI review" | |
| else | |
| echo "has_suggestions=true" >> $GITHUB_OUTPUT | |
| git diff > /tmp/ai-suggestions.patch | |
| if git apply --check /tmp/ai-suggestions.patch 2>&1; then | |
| echo "::notice::Valid patch generated — $(git diff --stat | tail -1)" | |
| else | |
| echo "has_suggestions=false" >> $GITHUB_OUTPUT | |
| echo "::error::Generated patch cannot be cleanly applied" | |
| fi | |
| fi | |
| - name: Apply AI suggestions | |
| if: steps.parse.outputs.has_suggestions == 'true' | |
| run: | | |
| git checkout -- . | |
| git apply /tmp/ai-suggestions.patch | |
| - name: Run linters | |
| if: steps.parse.outputs.has_suggestions == 'true' | |
| run: | | |
| pip install ruff 2>/dev/null || true | |
| if command -v ruff &> /dev/null; then | |
| ruff check . --fix || true | |
| ruff format . || true | |
| fi | |
| - name: Run secret scanning | |
| if: steps.parse.outputs.has_suggestions == 'true' | |
| run: | | |
| if git diff | grep -iE "(api[_-]?key|password|secret|token)" > /dev/null; then | |
| echo "::warning::Potential secret detected in changes — manual review required" | |
| fi | |
| - name: Create Draft Pull Request | |
| id: create_pr | |
| if: steps.parse.outputs.has_suggestions == 'true' | |
| uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: | | |
| AI-suggested improvements from PR #${{ github.event.pull_request.number }} | |
| Generated by Claude Code Review. | |
| Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> | |
| branch: ai/auto-fix-pr-${{ github.event.pull_request.number }}-${{ github.run_id }} | |
| delete-branch: true | |
| draft: true | |
| title: "AI Suggestions: ${{ github.event.pull_request.title }}" | |
| body: | | |
| ## Automated Improvements | |
| This draft PR contains AI-suggested improvements for PR #${{ github.event.pull_request.number }}. | |
| ### Review Process | |
| 1. **Claude Code Review**: Analyzed code and generated suggestions | |
| 2. **Security Checks**: Passed pre-flight validation | |
| 3. **Patch Application**: Applied cleanly | |
| ### Important | |
| - **This is a DRAFT PR** — requires human review before merge | |
| - **Do not auto-merge** — maintainer approval required | |
| - Review all changes carefully before approving | |
| - CI won't run automatically (GITHUB_TOKEN created PR) — push a commit or close/reopen to trigger | |
| --- | |
| Generated by [Claude Code](https://claude.com/claude-code) | Auto-Fix (opt-in via `ai-fix` label) | |
| labels: | | |
| ai-generated | |
| needs-human-review | |
| reviewers: ${{ github.event.pull_request.user.login }} | |
| - name: Comment on original PR | |
| if: steps.create_pr.outputs.pull-request-number | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const prNumber = '${{ steps.create_pr.outputs.pull-request-number }}'; | |
| const prUrl = '${{ steps.create_pr.outputs.pull-request-url }}'; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: `## AI Suggestions Available | |
| I've analyzed this PR and created a draft PR with suggested improvements: **#${prNumber}** | |
| [View Draft PR with AI Suggestions](${prUrl}) | |
| ### Next steps | |
| 1. Review the suggested changes in draft PR #${prNumber} | |
| 2. CI won't run automatically — push a commit or close/reopen to trigger checks | |
| 3. If approved, merge the draft PR | |
| **The draft PR requires human approval** — do not auto-merge. | |
| --- | |
| Claude Code Review | Auto-Fix` | |
| }); | |
| blocked-security: | |
| name: Security Block Notice | |
| needs: security-check | |
| if: | | |
| needs.security-check.outputs.safe_to_proceed == 'false' && | |
| github.event.action == 'labeled' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Post security notice | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const reason = `${{ needs.security-check.outputs.blocked_reason }}`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: `## AI Patch Generation Blocked | |
| ${reason} | |
| **Security Policy**: Automated patch generation is disabled for: | |
| - Workflow files (.github/workflows/*) | |
| - Installer code (dream-server/installers/*) | |
| - CLI tool (dream-server/dream-cli) | |
| - Configuration files (dream-server/config/*) | |
| - Secrets and credentials | |
| - PRs from forks | |
| You can still get a review comment via the basic review (triggered on PR open/sync).` | |
| }); | |
| # Required secrets: | |
| # - GITHUB_TOKEN (automatically provided) | |
| # - ANTHROPIC_API_KEY (for Claude Code review) |