Portal issues now file with only the 'portal' label. The watcher runs two phases:
1. Triage — Claude reads each untriaged 'portal' issue (analysis only), posts a
requirements-breakdown comment, and routes it to 'claude-queue' (auto-fixable)
or 'interactive' (needs human steering).
2. Fix — unchanged; processes 'claude-queue' issues into PRs.
The triage breakdown is posted without the bot marker so the fix stage reads it
back as refined requirements.
PS 5.1 fixes found while validating:
- Send API bodies as UTF-8 bytes (Invoke-RestMethod mangled non-ASCII, e.g. the
em-dash in Claude's breakdown, so Forgejo rejected the JSON)
- Build the labels array body by hand (ConvertTo-Json unwraps a single-element
array to a scalar, which Forgejo rejects)
- Triage output via two plain files (label + markdown) instead of one JSON blob
(embedded-newline markdown broke ConvertFrom-Json)
- Read triage files as UTF-8; additive label POST + a guard so Set-IssueLabels
can never wipe an issue's labels
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
5.1 KiB
Markdown
99 lines
5.1 KiB
Markdown
# Automated issue-to-deploy pipeline
|
|
|
|
End-to-end flow from a user clicking **Report Issue** in the portal to a fix
|
|
running in production:
|
|
|
|
```
|
|
Portal header (bug icon) [App/components/layout/report-issue-button.tsx]
|
|
│ server action → Forgejo API
|
|
▼
|
|
Forgejo issue (label: portal) [git.pelagiamarine.com/shad0w/pelagia-portal]
|
|
│ polled every 10 min by Windows Scheduled Task "PelagiaClaudeIssueWatcher"
|
|
▼
|
|
TRIAGE (watcher phase 1) [dev PC, headless Claude Code, analysis only]
|
|
│ Claude reads the issue + repo, posts a requirements-breakdown comment,
|
|
│ and routes it: adds `claude-queue` (auto-fixable) or `interactive` (human)
|
|
▼
|
|
FIX (watcher phase 2, only for claude-queue) [headless Claude Code in C:\...\src\pelagia-autofix]
|
|
│ Claude implements + verifies fix; watcher pushes branch claude/issue-N
|
|
│ and opens a PR (label: claude-pr)
|
|
▼
|
|
Human review: merge the PR, then create a release tag vX.Y.Z
|
|
│ tag push triggers .forgejo/workflows/deploy.yml
|
|
▼
|
|
forgejo-runner on pms1 (pm2: forgejo-runner, label "host")
|
|
│ checks out the tag in ~/pms, pnpm install + build + prisma migrate deploy
|
|
▼
|
|
pm2 restart ppms → live at pms.pelagiamarine.com
|
|
```
|
|
|
|
`interactive`-routed issues stop after triage for a human to pick up (run with
|
|
Claude in a steered session). The triage breakdown comment is plain (no bot
|
|
marker) so, for `claude-queue` issues, the fix stage reads it back as refined
|
|
requirements.
|
|
|
|
## Components
|
|
|
|
| Piece | Where | Notes |
|
|
|---|---|---|
|
|
| Report Issue button | `App/components/layout/report-issue-button.tsx` + `report-issue-actions.ts` | Any signed-in user; files issue with only the `portal` label (triage routes it) |
|
|
| Forgejo helper | `App/lib/forgejo.ts` | Needs `FORGEJO_URL`, `FORGEJO_REPO`, `FORGEJO_TOKEN` env (token scope: `write:issue`) |
|
|
| Issue watcher | `automation/claude-issue-watcher.ps1` | Config in `watcher.config.json` (gitignored — copy from the example). Logs in `automation/logs/` |
|
|
| Scheduled task | `automation/register-watcher-task.ps1` | Registers `PelagiaClaudeIssueWatcher`, every 10 min, single-instance |
|
|
| Deploy workflow | `.forgejo/workflows/deploy.yml` | Triggers on `v*` tags; runs on the `host` runner |
|
|
| Runner | pms1 `~/forgejo-runner`, pm2 process `forgejo-runner` | Registered as `pms1-host` with labels `host`, `docker` |
|
|
|
|
## Issue label lifecycle
|
|
|
|
```
|
|
portal ──(triage)──▶ claude-queue ─▶ claude-working ─▶ claude-pr | claude-failed
|
|
└────▶ interactive (stops here — handle with Claude interactively)
|
|
```
|
|
|
|
- A `portal` issue with no decision label yet is triaged once (`maxTriagePerRun`
|
|
per run). Triage adds `claude-queue` or `interactive` and posts a breakdown.
|
|
- `claude-queue` → `claude-working` → `claude-pr` (PR opened) or `claude-failed`.
|
|
- To retry a failed issue, re-add `claude-queue`.
|
|
- To queue any manually-created issue for Claude (skipping triage), add
|
|
`claude-queue` directly. To force human handling, add `interactive`.
|
|
- Triage is skipped for issues that already carry any decision label.
|
|
|
|
## Releasing
|
|
|
|
After merging a Claude PR (or any change) on `master`:
|
|
|
|
```powershell
|
|
git pull
|
|
git tag v0.2.0 # semver: bump patch for fixes, minor for features
|
|
git push pms1 master --tags
|
|
```
|
|
|
|
The runner deploys the tag and restarts the app. Watch progress under
|
|
**Actions** on the Forgejo repo, or `pm2 logs forgejo-runner` on pms1.
|
|
|
|
## Operational notes
|
|
|
|
- The watcher runs Claude Code with `--dangerously-skip-permissions` inside the
|
|
dedicated `pelagia-autofix` clone — never point `workDir` at your main checkout.
|
|
- Watcher only works issues while this PC is on; queued issues are picked up on
|
|
the next run after boot (`-StartWhenAvailable`).
|
|
- Tokens: `portal-report-issue` (write:issue, used by the app) and
|
|
`claude-watcher` (write:issue + write:repository, used by the watcher).
|
|
Both belong to the `shad0w` Forgejo account. Rotate via
|
|
`docker exec -u 1000 forgejo forgejo admin user generate-access-token ...`.
|
|
- Server-side env for the button lives in `~/pms/App/.env` on pms1
|
|
(`FORGEJO_URL=http://127.0.0.1:3001` so it does not depend on the tunnel).
|
|
- **Known Forgejo 10 bug:** clicking *Update branch* on a PR (or pushing to its
|
|
head branch) can make the page show "This pull request is broken due to
|
|
missing fork information" even though the PR is fine (API still reports
|
|
`mergeable: true`). Fix: close and reopen the PR — via the UI, or:
|
|
|
|
```powershell
|
|
$h = @{ Authorization = "token <claude-watcher token>" }
|
|
Invoke-RestMethod -Method Patch -Headers $h -ContentType application/json `
|
|
-Uri https://git.pelagiamarine.com/api/v1/repos/shad0w/pelagia-portal/pulls/<N> -Body '{"state":"closed"}'
|
|
Invoke-RestMethod -Method Patch -Headers $h -ContentType application/json `
|
|
-Uri https://git.pelagiamarine.com/api/v1/repos/shad0w/pelagia-portal/pulls/<N> -Body '{"state":"open"}'
|
|
```
|
|
|
|
Fixed upstream in newer Gitea/Forgejo — resolves itself if Forgejo is upgraded past v10.
|