Claude Code agent that works Forgejo issues (composite action + runner setup)
This repository has been archived on 2026-10-17. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
  • Python 96%
  • Shell 4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ivan Pavlina e14ab87676 Resolve the open TODOs in CLAUDE.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 15:39:32 +00:00
.gitignore Trigger from a user-level webhook instead of per-repo Actions workflows 2026-10-15 08:34:24 +02:00
agent.py Let the agent try web GUIs in a browser, show the model in the footer 2026-10-15 19:50:21 +02:00
claude-agent.service Trigger from a user-level webhook instead of per-repo Actions workflows 2026-10-15 08:34:24 +02:00
CLAUDE.md Resolve the open TODOs in CLAUDE.md 2026-10-16 15:39:32 +00:00
grant-access.sh grant-access: never give the bot access to its own harness repo 2026-10-15 08:35:31 +02:00
mcp.json Let the agent try web GUIs in a browser, show the model in the footer 2026-10-15 19:50:21 +02:00
README.md Add CLAUDE.md, point README to it 2026-10-16 15:37:32 +00:00
webhook.py Also start on a new issue that mentions @claude 2026-10-15 08:55:48 +02:00

claude-agent

Claude Code working Forgejo issues in any of the owner's repositories. Assign an issue to the claude user, or mention @claude in a new issue or a comment, and a headless Claude run picks it up, commits to claude/issue-<n>, opens a PR and reports back on the thread. Follow-ups go as @claude ... comments on the issue or on the PR; each one is a fresh Claude run that gets the whole thread plus the existing branch.

Quick start

WEBHOOK_SECRET=secret LISTEN=127.0.0.1:8099 python3 webhook.py   # curl localhost:8099/healthz

Python 3 standard library only. Real runs also need CLAUDE_BOT_TOKEN, CLAUDE_CODE_OAUTH_TOKEN and FORGEJO_URL (see below). Developer and agent notes, gotchas: CLAUDE.md.

Pieces

What Where
Webhook receiver webhook.py, claude-agent.service
Harness (one run) agent.py
Host VM 106 claude-agent on Proxmox, 192.168.88.106, user claude, checkout in ~/claude-agent
Trigger user-level webhook on ivan.pavlina → http://192.168.88.106:8090/, events: Issues, Issue Assign, Issue Comment, Pull Request Comment
Bot account Forgejo user claude, collaborator (write) on each repo

Forgejo only delivers to private addresses listed in FORGEJO__webhook__ALLOWED_HOST_LIST (docksys compose).

A new repository

./grant-access.sh ivan.pavlina/<repo>   # no argument = all non-archived repos

The no-argument run skips claude-agent itself: the bot must not propose changes to its own harness.

Without access the bot answers on the issue and asks for it.

Deploying a change

ssh claude@192.168.88.106 'cd ~/claude-agent && git pull && sudo systemctl restart claude-agent'

There is no CI for this repo; deploying is the pull and restart above. Run logs: ~/runs/<timestamp>-<owner>_<repo>-<n>.log (payload next to it, kept 30 days), service log: journalctl -u claude-agent.

The VM

  • Debian 13 cloud image, 4 vCPU (affinity 12-19, E-cores), 8 GB RAM.
  • scsi0 40G: OS, ~/.claude (settings, skills, memory, sessions), this checkout, ~/runs, secrets. Included in the nightly vzdump to pbs-local.
  • scsi1 100G, backup=0, mounted at /cache: run checkouts (TMPDIR=/cache/runner), Docker data-root (/cache/docker), and ~/.cache, ~/.npm, ~/go (symlinks into /cache/home). Losing it costs nothing but rebuild time.
  • Secrets in ~/.config/claude-agent/agent.env (0600): CLAUDE_BOT_TOKEN (bot user), CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token, subscription auth), WEBHOOK_SECRET, FORGEJO_URL.

Containment

  • Only logins in ALLOWED_USERS (default ivan.pavlina) can trigger a run, and every delivery must carry a valid HMAC signature.
  • The bot token is stripped from Claude's environment and kept out of the clone's remote URL, so Claude does not push; the harness pushes, only to the work branch, never to the default branch. This is not a hard boundary: Claude runs as the same user and could read agent.env from disk. Acceptable here because only the owner can trigger runs and the bot can only reach branches and PRs of repos it was added to; the real fix would be a separate Unix user.
  • Tools: file reads/edits plus the Bash allowlist ALLOWED_TOOLS in agent.py. Anything else is denied and listed in the run log. A repo can allow more in its own .claude/settings.json.
  • Browser: where /usr/bin/chromium exists, the run gets the Playwright MCP server from mcp.json (headless, isolated profile) to try web GUIs on desktop and phone sizes.
  • Permission mode: CLAUDE_PERMISSION_MODE (default auto); effort: CLAUDE_EFFORT (unset = CLI default). Both go in agent.env.