Sessions run claude with --dangerously-skip-permissions and a prompt built from issue/PR/comment text, so only trusted authors may supply that text. teabot now dispatches a job only when the triggering event's author login is on an allowlist; an empty allowlist dispatches nothing (fail-closed). Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
4.3 KiB
AGENTS.md
Project Overview
teabot is a Go daemon (systemd user service) that watches Gitea repositories
and dispatches one-shot Claude Code sessions in Docker containers to work
issues and review pull requests. It polls the Gitea API for new issues, pull
requests, and comments; for each event it runs a throwaway container that clones
the repo and runs claude --print with a task-specific prompt, acting as a
configurable bot personality.
Structure
main.go # package main; wires version into internal/cli
internal/cli/ # cobra command tree: run, config init/show
internal/config/ # config.yaml + tea-config parsing, defaults, validation
internal/gitea/ # read-only Gitea REST client (issues/pulls/comments/diff)
internal/state/ # processed-event persistence (JSON, atomic writes)
internal/prompt/ # per-event prompt construction (issue/pull/follow-up)
internal/dispatch/ # poll loop, event filtering, loop prevention, job orchestration
internal/docker/ # containerised Claude execution behind a Runner interface
docs/ # architecture + configuration + per-subcommand docs
packaging/nfpm.yaml # RPM spec (binary + completions + systemd unit + example config)
scripts/build-rpm.sh # generates completions + packages the RPM with nfpm
systemd/teabot.service # systemd user unit
config.example.yaml # example config (also embedded for `config init`)
.woodpecker/ # CI: build, test, pre-commit (PR) + release (tag)
Build / test
make build # -> dist/teabot (CGO disabled, static)
make test # go test -race ./...
make rpm # build + package RPM (needs nfpm)
Requires Go 1.25+. Deps: github.com/spf13/cobra, gopkg.in/yaml.v3.
Design notes for contributors
- Loop prevention is load-bearing. Any event authored by a personality's Gitea username is skipped (but recorded). Never remove this — it is what stops the bot reacting to its own PRs/comments forever.
- Author allowlist is a security control. Jobs run
--dangerously-skip-permissionswith a prompt built from event text, so teabot only dispatches for authors inallowed_authors(optional per-repo override). Fail-closed: empty list dispatches nothing. Comment follow-ups gate on the new comment's author, not just the acted-on thread. Skipped events are marked processed-only (never acted-on). - Dedup before dispatch. Issues/PRs/comments are marked processed in the
state store before their job starts, so a re-poll or mid-job restart cannot
double-launch. State lives at
~/.local/state/teabot/state.json. - First-contact seeding. The first poll of a repo records existing open items as processed without dispatching, so a fresh install doesn't stampede.
- Follow-ups only on acted threads. Comment follow-ups fire only when teabot previously opened a PR for / reviewed the parent issue/PR.
- Docker is behind
docker.Runner. All dispatch logic is tested with a fake runner; no Docker daemon is needed forgo test.DockerRunner.buildArgsis pure given a job dir so thedocker runargument list is unit-tested directly. - Personalities use tea configs. tea persists to
$XDG_CONFIG_HOME/teaand has no--configflag, so each personality's config is mounted into the container andXDG_CONFIG_HOMEis set there. - Image reuse. The default
job_imageisagent-dev, which already ships the claude CLI + dev toolchain; teabot does not build its own image.
Testing conventions
Every package has meaningful unit tests (no rubber-stamps): config precedence &
validation & tea parsing, state persistence/dedup roundtrips, gitea client via
httptest, prompt content assertions, docker arg/mount/env construction, and
dispatch filtering/loop-prevention/seeding/follow-up routing via a fake client +
fake runner. go build ./... and go test -race ./... must pass.
Conventions (house rules)
- Branches
benvin/<name>. PR bodies: short "why" paragraph + present-tense "how" bullets. HTTPS remotes for git.unkin.net (SSH blocked). Usetea, notgh. Do not merge PRs yourself. - Releases are Woodpecker pipelines triggered on
v*tags; bump withmake patch|minor|major. Every CI step sets k8s resource requests+limits.