A Forgejo Action that runs [opencode](https://opencode.ai) against a Nomyo (or any OpenAI-compatible) backend, triggered by comments on issues and merge requests.
Mention `/opencode` or `/oc` in a comment and opencode reads the thread, executes the requested task, and replies. For "fix" tasks it opens a merge request; for MR comments it commits to the same MR.
The action uses **two** Forgejo PATs with split duties — see [Security](#security) for the rationale. Generate both at *Settings → Applications → Manage Access Tokens* (Forgejo's write scope inherently grants read on the same resource):
**Read PAT** — used by the outer action for fetches and read APIs. Never exposed to the opencode subprocess.
Add `.forgejo/workflows/opencode.yml` to the repo where you want opencode to respond. This example targets a Forgejo instance hosted under a subpath (`bitfreedom.net/code/`); adjust URLs and `runs-on` to match your setup.
`act_runner`'s `uses:` parser expects `https://<host>/<owner>/<repo>` — it does not handle a Forgejo instance hosted under a subpath (e.g. `bitfreedom.net/code/`). For subpath instances, clone the action source into `./.opencode-action` and reference it as a local action (`uses: ./.opencode-action`). If your Forgejo lives at a domain root, you can replace those two steps with `uses: <your-host>/nomyo-ai/actions@v1` directly.
| `model` | Yes | — | Model in the form `provider/model`. The provider name becomes a custom opencode provider; the action registers it as an `@ai-sdk/openai-compatible` provider pointing at `nomyo_api_url`. |
| `nomyo_api_key` | Yes | — | API key for the OpenAI-compatible backend. |
| `nomyo_api_url` | No | `https://chat.nomyo.ai/api` | Base URL of the OpenAI-compatible endpoint. The adapter calls `${baseURL}/chat/completions`. |
| `forgejo_api_url` | No | `https://bitfreedom.net/code/` | Forgejo instance base URL. |
| `forgejo_token` | No | — | Forgejo PAT used by the outer action for read ops (clone, fetch, read APIs). Stripped from the opencode subprocess env. |
| `forgejo_push_token` | No | falls back to `forgejo_token` | Optional separate write PAT used only after the agent finishes (git push, comment write, MR create). |
2. Runs [`bun install`](package.json) inside the action source.
3. Registers `${MODEL%/*}` as a custom opencode provider via `OPENCODE_CONFIG_CONTENT` and the API key via `OPENCODE_AUTH_CONTENT`.
4. Spawns `opencode serve` locally and talks to it over HTTP (`/session/{id}/message`).
5. Resolves the event, builds a prompt with thread/diff context, sends it to opencode, and posts the response as a comment (and, for "fix" intents, opens an MR).
The agent runs untrusted-ish code (model output executes shell commands, edits files, etc.). The action takes the following defensive measures:
- **Two-token model.** `FORGEJO_TOKEN` is read-only, used by the outer process for fetches and read APIs. `FORGEJO_PUSH_TOKEN` is write-capable and is only loaded into the outer process — it is never placed into the opencode subprocess environment, and it is never written to `.git/config`.
- **Env stripping.** When the action spawns `opencode serve`, the child env is filtered: `FORGEJO_TOKEN`, `FORGEJO_PUSH_TOKEN`, and `GITHUB_TOKEN` are removed. The agent's `bash` tool inherits opencode's env, so these variables are unreachable from any shell the agent runs.
- **Per-command git auth.** Credentials for `git fetch` / `git push` are passed via `git -c http.<host>.extraheader=...` on each invocation, not persisted in `.git/config`. A jailbroken agent cannot `git push` even with a valid remote.
- **Nomyo key exposure (unavoidable).** `OPENCODE_AUTH_CONTENT` (containing the Nomyo API key) must be in opencode's env for the model to work; the agent's bash tool can read it. Treat the Nomyo key as compromised from the agent's perspective and rotate accordingly.