# Forgejo Platform Support This action supports [Forgejo](https://forgejo.org/) (and Forgejo Actions) as an alternative to GitHub. The same action code runs on both platforms — platform detection is automatic or explicitly configured. ## Prerequisites - **Forgejo instance** with [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/) enabled - **Forgejo runner** registered and operational (e.g., a `grunt` or `ubuntu-latest` runner) - **Anthropic API key** or **Claude Code OAuth token** for Claude API access - **Forgejo token** with repo access (automatically available as `github.token` in workflows, or use a [dedicated bot user](#dedicated-bot-user-recommended)) ## Quick Start ### 1. Add Secrets Add secrets at the **organization level** (Settings > Actions > Secrets) so all repos can share them: - `ANTHROPIC_API_KEY` — Your Anthropic API key (starting with `sk-ant-`) - `CLAUDE_BOT_TOKEN` — API token for the dedicated bot user (see [Dedicated Bot User](#dedicated-bot-user-recommended) below) ### 2. Create Workflow Create `.forgejo/workflows/claude.yml`: ```yaml name: Claude Assistant on: issue_comment: types: [created] pull_request_review_comment: types: [created] issues: types: [opened, assigned, labeled] pull_request_review: types: [submitted] jobs: claude-response: runs-on: grunt # Your Forgejo runner label steps: - uses: https://git.wxs.ro/actions/claude-code-action@v1 # Or your mirror URL with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} platform: "forgejo" github_token: ${{ secrets.CLAUDE_BOT_TOKEN }} bot_id: "YOUR_BOT_ID" # Replace with your bot's Forgejo user ID (see step 5 below) bot_name: "claude" bot_email: "claude@yourdomain.com" # Must match the bot user's registered email ``` ### 3. Agent Mode (Automation) For automation workflows triggered by `workflow_dispatch`, push events, or schedules: ```yaml name: Claude Automation on: workflow_dispatch: inputs: task: description: "Task for Claude" required: true jobs: run-claude: runs-on: grunt steps: - uses: https://git.wxs.ro/actions/claude-code-action@v1 with: prompt: ${{ github.event.inputs.task }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} platform: "forgejo" github_token: ${{ secrets.CLAUDE_BOT_TOKEN }} bot_id: "YOUR_BOT_ID" # Replace with your bot's Forgejo user ID bot_name: "claude" bot_email: "claude@yourdomain.com" ``` ## Platform Detection The action detects the platform using a three-tier strategy: | Priority | Method | Details | | -------- | --------------------------------- | -------------------------------------------------------- | | 1 | **Explicit input** | `platform: "forgejo"` in workflow — most reliable | | 2 | **`FORGEJO` env var** | Set to `"true"` by Forgejo runner v7.0+ | | 3 | **`GITHUB_SERVER_URL` heuristic** | If the URL doesn't contain `github.com`, assumes Forgejo | **Recommendation**: Always set `platform: "forgejo"` explicitly. Auto-detection works but the explicit input eliminates ambiguity. ## Differences from GitHub ### Authentication | Aspect | GitHub | Forgejo | | ------------------------ | ------------------------------- | ------------------------------------------------------------- | | **Default auth** | GitHub App OIDC token exchange | Direct `GITHUB_TOKEN` / `FORGEJO_TOKEN` | | **Token setup** | Automatic via Claude GitHub App | Manual — provide `github_token` input | | **App token revocation** | Automatic cleanup step | Skipped (no app token) | | **Bot identity** | `claude[bot]` (ID: 41898282) | Dedicated bot user via `bot_id`/`bot_name`/`bot_email` inputs | On Forgejo, there is no OIDC token exchange with Anthropic. The action uses the workflow token (`github.token`) or a user-provided `github_token` directly. ### API Differences | Feature | GitHub | Forgejo | | --------------------- | ------------------------------------------- | ------------------------------------------------------------------- | | **GraphQL API** | Used for PR/issue data fetching | Not available — REST API only | | **Data fetching** | Single GraphQL query per entity | 5-8 parallel REST API calls | | **Actor validation** | Checks for bot/app accounts | Skipped — no bot accounts on Forgejo | | **GitHub MCP server** | `ghcr.io/github/github-mcp-server` included | Skipped — GitHub-specific | | **CI/Actions MCP** | Full GitHub Actions API | Works via Octokit REST + `GITHUB_API_URL` | | **Permission check** | `getCollaboratorPermissionLevel()` | `repos.get()` fallback (workflow tokens lack collaborator endpoint) | | **Git ref lookup** | `git.getRef()` (`/git/ref/heads/...`) | `repos.getBranch()` (`/git/refs` is plural on Forgejo) | ### Bun Installation The upstream action uses `oven-sh/setup-bun`. On Forgejo, this is replaced with a universal `curl` install because Forgejo runners pre-fetch all `uses:` action references before evaluating `if:` conditions, causing failures for GitHub-hosted actions that aren't mirrored. ### Comment URLs Comment tracking URLs (job links, branch links, PR creation links) use Forgejo URL patterns instead of GitHub patterns. This is handled automatically. ## Action Inputs The `platform` input is the only Forgejo-specific addition: ```yaml platform: description: "Platform to run on: 'auto' (default auto-detects), 'github', or 'forgejo'" required: false default: "auto" ``` All other inputs work identically on both platforms. See the [Usage Guide](./usage.md) for the full list. ## Dedicated Bot User (Recommended) On GitHub, Claude gets its own identity via the GitHub App (`claude[bot]`). On Forgejo, there's no such mechanism — by default the action runs under whatever token you provide, meaning commits and comments appear as the human user. Creating a dedicated bot user solves this: - **Clear attribution**: Commits and comments show as `claude`, not your personal account - **Least privilege**: The bot token has only the permissions Claude needs - **Audit trail**: Easy to distinguish human actions from bot actions in repo history - **Security**: If the action is compromised, only the bot's scoped token is exposed ### Setup Steps #### 1. Create the bot user Go to **Site Administration > User Accounts > Create User Account** or use the admin API: - **Username**: `claude` - **Email**: e.g., `claude@yourdomain.com` - **Password**: any strong password (you'll use API tokens, not password login) - **Uncheck** "Require user to change password" - Leave "Admin" **unchecked** - Optionally set a display name and avatar to distinguish from human users #### 2. Add to organizations via teams For each organization where Claude should operate: 1. Go to **Org Settings > Teams > New Team** 2. Create a team named `Claude` with: - **Permission**: `Write` - **Access**: All Repositories (so new repos are automatically covered) 3. Add `claude` to the team's **Members** #### 3. Generate a scoped API token Log in as `claude`, go to **Settings > Applications**, and create a token: - **Token Name**: `claude-code-action` - **Scopes**: - `repo` — read/write (issues, PRs, comments, branches, commits) - `user:read` — needed for API identity - Leave `admin`, `organization`, `package` **unchecked** #### 4. Store the token as an org-level secret Add the token as `CLAUDE_BOT_TOKEN` at the **organization level** (Settings > Actions > Secrets) with visibility set to "All repositories". This way all repos in the org share the same secret. #### 5. Get the bot user's ID You'll need the Forgejo user ID for the `bot_id` input: ```bash curl -s "https://your-forgejo-instance/api/v1/users/claude" | jq '.id' ``` #### 6. Configure the workflow ```yaml - uses: https://git.wxs.ro/actions/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} platform: "forgejo" github_token: ${{ secrets.CLAUDE_BOT_TOKEN }} bot_id: "YOUR_BOT_ID" # Replace with the ID from step 5 bot_name: "claude" bot_email: "claude@yourdomain.com" # Must match the bot user's registered email ``` The `bot_email` input overrides the default noreply email format. When it matches the bot user's registered email in Forgejo, commits will be linked to the bot's profile in the UI. ### Token Rotation Forgejo API tokens can expire. Plan for rotation: 1. **Set a calendar reminder** for before the token expires 2. **Generate a new token** by logging in as `claude` (same scopes as above) 3. **Update the org secret** `CLAUDE_BOT_TOKEN` with the new token value 4. **No code changes needed** — the workflow reads the secret at runtime Alternatively, generate a token without an expiration date (if your Forgejo instance allows it). ## Included CI Workflows This fork includes four Forgejo-native workflows in `.forgejo/workflows/`: ### `claude.yml` — Tag Mode (@claude Mentions) Responds to `@claude` mentions on issues, PR review comments, and PR reviews. Also triggers on issue assignment and label events. This is the main interactive workflow for using Claude on Forgejo. ### `ci.yml` — Tests, Formatting, Type Checking Runs on push to `main` and pull requests. Three parallel jobs: - `test` — `bun test` (with `GITHUB_SERVER_URL` override so GitHub-context unit tests pass) - `prettier` — `bun run format:check` - `typecheck` — `bun run typecheck` ### `test-action.yml` — Action Self-Test Tests the action itself on Forgejo in agent mode. Runs weekly and on PRs that modify `src/`, `action.yml`, or `.forgejo/`. Validates: - Platform detection returns "forgejo" - Token authentication works - Claude Code executes successfully - Output file is produced ### `upstream-sync.yml` — Upstream Sync Check Runs weekly. Compares `main` with `upstream/main` (anthropics/claude-code-action). If upstream has new commits: - Creates or updates an issue with the `upstream-sync` label - Includes commit log, diff stats, and merge instructions - Lists conflict-prone files ## Merging Upstream Changes This fork is designed for easy upstream merges. Only 4-5 files have Forgejo-specific modifications, and changes are placed at function entry points using an early-return pattern. ### Merge Process ```bash # Fetch latest upstream git fetch upstream main # Merge into your main branch git merge upstream/main # If conflicts occur, they'll be in these files: # - src/entrypoints/run.ts (platform branch at top) # - src/modes/tag/index.ts (data fetcher swap) # - src/mcp/install-mcp-server.ts (platform routing) # - action.yml (platform input, bun install) # Resolve conflicts, keeping both the upstream changes and # the Forgejo platform branches at function entry points # Run tests to verify bun test # Push git push origin main ``` ### Conflict Mitigation All Forgejo changes follow the **early-return pattern** — platform-specific logic is placed at the top of functions, before the existing GitHub code. This means: - Upstream changes to the GitHub code path rarely conflict with Forgejo additions - When conflicts do occur, they're localized to the few lines where the platform branch starts - Resolution is straightforward: keep both the platform check and the upstream changes ### What Happens When Upstream Adds New Features New upstream features that use GraphQL or GitHub-specific APIs won't automatically work on Forgejo. The Forgejo data fetcher (`src/forgejo/data/fetcher.ts`) would need REST equivalents added. The action will still run — features that depend on missing data will simply lack that context. ## E2E Test Results Both execution modes have been validated end-to-end on a Forgejo instance (git.wxs.ro): | Mode | Trigger | Status | Details | | -------------- | --------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Agent mode** | `workflow_dispatch` | Validated | Platform detection, token auth, Claude execution, output file — all green | | **Tag mode** | `issue_comment` (`@claude`) | Validated | Issue context (title + body) read correctly, platform detected as `forgejo`, tracking comment created with spinner then updated with response (31s), job link URL correct | ### Tag Mode Verified Capabilities - `@claude` trigger phrase detected in issue comments - Issue body and title fully visible to Claude - Tracking comment created with progress spinner, then updated with final response - Forgejo job run URL correctly formatted in tracking comment - Branch creation works via `repos.getBranch()` API (Forgejo uses `/git/refs` not `/git/ref`) - Permission check passes via `repos.get()` fallback ### Not Yet E2E Tested - PR review comment triggers (`pull_request_review_comment`) - PR review triggers (`pull_request_review`) - Label triggers (`issues` with `labeled` action) - Assignee triggers (`issues` with `assigned` action) - Branch creation + code changes from tag mode (issue asked a question, no code changes needed) ## Known Limitations - **Git ref API**: Forgejo uses `/git/refs` (plural) instead of GitHub's `/git/ref` (singular). The action handles this by using `repos.getBranch()` on Forgejo for branch SHA lookups. - **User display name fetch**: `GET /users/{username}` returns 403 without `read:user` scope. Non-fatal — falls back to login name. - **`pull_request_target` event**: May not exist in Forgejo. The action degrades gracefully (maps to `pull_request`). - **GitHub MCP server**: The `ghcr.io/github/github-mcp-server` Docker image is GitHub-specific and is skipped on Forgejo. - **Commit signing**: SSH signing has not been tested on Forgejo. The `use_commit_signing` option (GitHub API signing) won't work since it uses GitHub's commit API. - **`downloadJobLogForWorkflowRun`**: Forgejo's Actions API may lack this endpoint. The CI MCP server attempts it via Octokit REST but may return limited data. - **Bot filtering**: `allowed_bots` checks won't match `claude[bot]` since Forgejo has no bot accounts. Actor validation is skipped entirely on Forgejo. ## Troubleshooting ### "Resource not accessible by integration" errors This usually means the token lacks permissions. On Forgejo: - Use a personal access token with `repo` scope via the `github_token` input - The default `github.token` in Forgejo workflows may have limited permissions depending on your runner configuration ### Tests fail with platform detection errors Unit tests assume GitHub context by default. When running on a Forgejo runner, set `GITHUB_SERVER_URL=https://github.com` before running tests (the CI workflow handles this automatically). ### Bun installation fails If `curl -fsSL https://bun.sh/install | bash` fails on your runner, pre-install Bun on the runner image or use the `path_to_bun_executable` input to point to a custom Bun binary. ### Upstream workflows run and fail If `.github/workflows/` from upstream are executing on your Forgejo runner, configure the runner to only read from `.forgejo/workflows/`. This is a runner-level setting — see your Forgejo instance's runner configuration.