Self-hosted AI dev agent for Gitea Actions — mention
@mokoin any issue and Claude responds, brainstorms requirements, then opens a PR.
You: opens an issue:
"Add a /now endpoint returning current ISO timestamp. @moko"
↓ (Gitea Actions cold-start ~30s)
Bot @moko: "🤔 thinking..." ← live status comment
↓ (Claude reads the issue + comment history + repo)
Bot @moko: "Need to confirm 3 things before I write code:
1. Which framework — FastAPI / Flask / standard lib?
2. Auth required?
3. Timezone — UTC or server-local?"
You: "@moko FastAPI, no auth, UTC"
↓ (Claude reads the full thread again, this time decides to implement)
Bot @moko: "Done — added `app/routes/now.py` with a single GET /now endpoint.
I'll prepare a PR."
📥 Opened PR: https://gitea.example.com/your-name/your-repo/pulls/42
- You stay in Gitea — no separate dashboard, no Slack bot, no IDE plugin
- Multi-turn requirements clarification — Claude asks before writing code when the request is vague (brainstorming-style)
- Auto PR — implements the change on a fresh branch, pushes, opens PR with the conversation transcript in description
- Self-hosted, your runner, your tokens — nothing leaves your infrastructure
- Model choice per call:
@moko[opus]/@moko[sonnet]/@moko[haiku]
┌─────────────┐ ┌────────────┐
│ Developer │ ① @moko in issue/PR comment │ Gitea │
└─────────────┘ ─────────────────────────────────────▶└─────┬──────┘
│ ② webhook
▼
┌─────────────────┐
│ act_runner │
│ (docker) │
└────────┬────────┘
│ ③ spawn job container
▼ (custom image w/ claude pre-installed)
┌───────────────────────────────────────┐
│ .gitea/workflows/claude.yml │
│ ↓ │
│ scripts/run-claude.sh │
│ ├ fetch issue + full comment thread │
│ ├ post "thinking..." status comment │
│ ├ exec `claude --print` w/ context │
│ ├ PATCH status comment → final reply│
│ └ if git diff → branch + push + PR │
└──────┬────────────────────────────────┘
│ ④ Gitea REST API (as moko bot)
▼
┌────────────┐
│ Gitea │ ⑤ developer sees moko's reply
└────────────┘
Full architecture in docs/01-architecture.md.
-
You already have a self-hosted Gitea + at least one act_runner. If not, see
docs/deployment.md. -
Build the custom runner image (one-time, ~3 min):
git clone https://github.com/tkpang/gitea-claude-agent.git cd gitea-claude-agent docker build -t local/gitea-runner-image:latest runner-image/ -
Configure the runner to use this image (
runner-data/.runnerlabels orconfig.yaml):runner: labels: - "ubuntu-latest:docker://local/gitea-runner-image:latest"
Restart act_runner.
-
Create a Gitea bot user (e.g.
moko) + access token withwrite:repository,write:user,write:issuescopes. Add the user as admin collaborator on each repo you want to enable. -
Get a Claude Code long-lived token:
claude setup-token # interactive, opens browser -
In the target Gitea repo, set two Actions secrets:
Name Value CLAUDE_CODE_OAUTH_TOKENfrom claude setup-token(~108 charssk-ant-oat-...)AGENT_GITEA_TOKENthe bot user's Gitea access token -
Copy
.gitea/workflows/claude.ymlandscripts/run-claude.shinto your target repo, push. -
Open an issue ending with
@moko. Watch Actions tab.
Full step-by-step in docs/deployment.md.
Default model is opus. Override per call:
@moko[sonnet] Fix the typo in README
@moko[haiku] What does this regex do?
@moko[opus] Refactor the auth module
@moko (no brackets — uses default)
To change the repo-wide default, uncomment MOKO_MODEL in .gitea/workflows/claude.yml.
🟡 MVP — works end-to-end on the author's setup. Light on tests. Sharp edges:
- Cold start ~30s per invocation (Gitea Actions overhead, not Claude)
- Status comment is a single edit-in-place comment (no GitHub-Checks-style ✅/⏳ — Gitea has no equivalent API)
- No PR diff line-comments yet (review mode); only issue/PR conversation
- One workflow run per
@mokomention — long brainstorming threads are okay but each round restarts the container
The longer-term plan (docs/02-roadmap.md) is to move to a long-running webhook service (Route B) to eliminate cold start. PRs welcome.
.
├── .gitea/workflows/claude.yml The Gitea Actions trigger + steps
├── scripts/run-claude.sh The actual orchestration (~250 lines bash)
├── runner-image/Dockerfile Custom job image (Debian + node + git + claude CLI pre-installed)
├── docs/
│ ├── 01-architecture.md
│ ├── 02-roadmap.md Route A (current) → Route B (long-running webhook)
│ ├── 03-porting-plan.md Origins: lift-and-shift from anthropics/claude-code-action
│ ├── 04-decisions.md ADRs
│ └── deployment.md Step-by-step self-host guide
└── examples/
├── docker-compose.yml Reference: Gitea + Postgres + act_runner
└── env.example
anthropics/claude-code-action is GitHub-only — its @octokit/* calls target the GitHub API. This project is a Gitea-native lift-and-shift of the same ergonomics:
| Feature | claude-code-action (GitHub) | gitea-claude-agent (this) |
|---|---|---|
@bot mention triggers |
✅ | ✅ |
| Multi-turn discussion | ✅ | ✅ |
| Auto-implement + PR | ✅ | ✅ |
| Status checklist on PR | ✅ (Checks API) | 🟡 single edit-in-place comment |
| Multi-mode (tag/agent/review) | ✅ | only tag mode for now |
| MCP server integration | ✅ | ⏳ planned |
| Runs on | GitHub Actions | Gitea Actions |
- anthropics/claude-code-action — the design we're lifting from
- go-gitea/gitea + act_runner — the CI substrate
- nektos/act — the inner engine inside act_runner
MIT — see LICENSE.