TerraScan: Self-Hosted AI Code Reviews for Gitea
If you’ve ever looked at AI code review tools like CodeRabbit or Codacy, you’ve probably noticed a pattern: GitHub and GitLab get all the love. If you’re running Gitea in your homelab, you’re mostly out of luck.
That’s why I built TerraScan — a self-hosted AI code reviewer that works natively with Gitea Actions. It runs as a Docker container, talks to OpenAI (or Anthropic, or even local Ollama models), and posts inline comments directly on your pull requests.
No third-party service. No sending your code to someone else’s servers (unless you count the AI API). Just a container you control.
What TerraScan Does
- Automatically reviews PRs when they’re opened or updated
- Posts a summary comment plus inline comments on specific lines
- Catches bugs, security issues, and code quality problems
- Supports OpenAI, Anthropic (Claude), and Ollama for local models
- Configurable severity thresholds — block only on critical issues, or run purely advisory
Prerequisites
Before you start, you’ll need:
- Gitea with Actions enabled (Gitea 1.25.4 (tested); 1.19+ with Actions enabled recommended)
- A Gitea runner with Docker access
- An OpenAI API key (or Anthropic key, or local Ollama instance)
- A Gitea token with repository write access (for posting comments)
Note: This guide uses OpenAI as the default provider. If you want to use Anthropic or Ollama, the setup is similar — just swap the API key environment variable.
Step 1: Understanding How It Works
Here’s the flow when a PR is opened:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PR Created
│
▼
Gitea Actions triggers workflow
│
▼
Workflow generates diff (git diff origin/main...HEAD)
│
▼
Diff is piped to TerraScan container
│
▼
Container sends diff to AI (OpenAI/Anthropic/Ollama)
│
▼
AI returns structured review
│
▼
Container posts comments via Gitea API
The container is stateless — all configuration comes from environment variables and the built-in config file.
Step 2: Setting Up Organization Secrets
Set these secrets at your Gitea organization level so all repos can use them:
- Go to your organization settings:
https://your-gitea/org/YOUR-ORG/settings/actions/secrets
Customize this URL: Replace
your-giteawith your Gitea instance domain andYOUR-ORGwith your organization name.
- Add these secrets:
| Secret | Description |
|---|---|
OPENAI_API_CODEREVIEW_KEY | Your OpenAI API key |
GTOKEN | Gitea personal access token with repo:write scope |
Tip: Setting secrets at the org level means you configure once and all repositories in that org can use them. You can also set per-repo secrets if needed.
Step 3: Creating the Workflow File
Add this job to your repository’s workflow file at .gitea/workflows/CICD.yml:
Important: The workflow below uses placeholder values for the Gitea registry URL (
your-gitea-instance.com) and container path (your-org/terrascan). Replace these with your own Gitea instance URL and the org/repo where you’re hosting the TerraScan container image.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
AIReview:
name: AI Code Review
# Only run on pull requests
if: gitea.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: https://gitea.com/actions/checkout@v6
with:
# Need full history for proper diff
fetch-depth: 0
- name: Run AI Code Review
run: |
BASE_REF="${{ gitea.event.pull_request.base.ref }}"
git fetch origin "$BASE_REF"
# Three-dot diff shows only changes in PR branch
# Falls back to two-dot if branches have diverged
if ! DIFF_CONTENT=$(git diff "origin/$BASE_REF"...HEAD 2>/dev/null); then
DIFF_CONTENT=$(git diff "origin/$BASE_REF"..HEAD 2>/dev/null) || exit 0
fi
# Skip if no changes
[ -z "$DIFF_CONTENT" ] && echo "No changes to review" && exit 0
# Pull and run the TerraScan container
# Replace 'your-gitea-instance.com' and 'your-org' with your values
echo "${{ secrets.GTOKEN }}" | docker login your-gitea-instance.com -u ${{ gitea.actor }} --password-stdin
docker pull your-gitea-instance.com/your-org/terrascan:latest
echo "$DIFF_CONTENT" | docker run --rm -i \
-e OPENAI_API_KEY=${{ secrets.OPENAI_API_CODEREVIEW_KEY }} \
-e GITEA_TOKEN=${{ secrets.GTOKEN }} \
-e REPO_NAME=${{ gitea.repository }} \
-e PR_NUMBER=${{ gitea.event.pull_request.number }} \
your-gitea-instance.com/your-org/terrascan:latest
Making It Play Nice With Other Jobs
Here’s a common scenario: you have a workflow with multiple jobs (Lint, Security, AIReview), and a final job that depends on all of them completing. The problem is that AIReview only runs on pull requests — so when you manually trigger the workflow via workflow_dispatch, AIReview gets skipped. By default, any job that lists AIReview in its needs: will also be skipped, even though Lint and Security completed successfully.
To fix this, use a condition that allows the downstream job to run when AIReview is skipped:
1
2
3
4
5
CheckMode:
needs: [Lint, Security, AIReview]
if: |
!cancelled() && !failure() &&
(gitea.event_name == 'pull_request' || gitea.event_name == 'workflow_dispatch')
This tells the job: “Run if nothing was cancelled or failed, and we’re either on a PR (where AIReview ran) or a manual dispatch (where it was intentionally skipped).”
Key Configuration Options
The container has sensible defaults, but here are the key environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
OPENAI_API_KEY | Yes* | — | OpenAI API key |
ANTHROPIC_API_KEY | Yes* | — | Alternative: Anthropic/Claude API key |
GITEA_TOKEN | Yes | — | Token with repo write access |
REPO_NAME | Yes | — | Set via ${{ gitea.repository }} |
PR_NUMBER | Yes | — | Set via ${{ gitea.event.pull_request.number }} |
*Provide one of OPENAI_API_KEY or ANTHROPIC_API_KEY, not both (unless using Ollama, then neither is required).
The built-in config (config/review-config.yml) controls:
- AI model selection
- Severity thresholds for blocking PRs
- File patterns to ignore
- Maximum comments per PR
Verifying It Works
- Create a new branch with some code changes
- Open a pull request
- Watch the Actions tab — you should see the “AI Code Review” job run
- Once complete, check your PR for:
- A summary comment at the top
- Inline comments on specific lines (if issues were found)
Example summary comment:
1
2
3
4
5
6
7
8
9
10
11
🤖 AI Code Review
## Summary
Reviewed 3 files with 47 lines changed.
### Issues Found
- 🟡 1 warning
- 🔵 2 suggestions
---
**Severity Guide:** 🔴 Critical | 🟠 Error | 🟡 Warning | 🔵 Info
Closing Thoughts
TerraScan fills a gap I kept running into: wanting AI-assisted code review without sending code to a third-party service, and without being locked into GitHub or GitLab.
If you’re running Gitea in your homelab, this gives you the same “open a PR, get feedback” workflow that commercial tools provide — just with infrastructure you control.
The project template is available on GitHub. Fork it, host it on your own Gitea instance, and customize to your needs. Drop a comment if you have questions about getting it running.
- Source: github.com/SpaceTerran/TerraScan
Update: Impact analysis (Jan 31, 2026)
Original post: Jan 30, 2026. TerraScan now supports optional impact analysis. When enabled (default), the bot runs a secondary AI pass before the main diff review: for each changed file it analyzes what’s affected, potential impacts, and what to verify, and finds related files (e.g. imports/references). That context is fed into the main reviewer, and the PR summary comment includes an Impact Analysis subsection with the identified impacts and files requiring attention. Inline comments can include code snippets for context. You can turn it off with impact_analysis_enabled: false in config/review-config.yml to use the original single-pass review. See the repo README for the new config options (impact_token_budget, impact_max_files, impact_include_references) and the “What’s new” section.
Note: OpenAI and GPT are trademarks of OpenAI. Anthropic and Claude are trademarks of Anthropic. Gitea is a trademark of Gitea Limited. CodeRabbit and Codacy are trademarks of their respective owners. This project is not affiliated with or endorsed by any of these companies.
