3x-ui is a Go (Gin + GORM) web panel that generates configuration, share links and subscriptions for other programs — Xray-core, mihomo, sing-box, mtg-multi — and is deployed by operators who upgrade in place. Judge findings by what breaks for those consumers and operators, not by style.
Mark every finding with exactly one of these, at the start of the finding:
| Marker | Severity | Use it for |
|---|---|---|
| 🔴 | Important | A defect this pull request introduces or makes worse, in one of the classes under "What Important means here". Worth fixing before it merges. |
| 🟡 | Nit | Style, naming, refactoring, and an ordinary CLAUDE.md violation the change introduces — a source comment block over two lines, a fix larger than the bug it removes, a test CLAUDE.md rejects outright. |
| 🟣 | Pre-existing | A real bug you hit while reading that this pull request neither introduced nor made worse. |
Not every CLAUDE.md rule is a nit. The three listed below — the dispatch
rule, the migration rule, the endpoint chain — are Important, because each one
passes every local test and breaks a real deployment.
Severity follows what this pull request did, not how alarming the defect looks on its own. One the change worsens is 🔴 for the regression it added, not for the whole defect; one it merely brought into view is 🟣.
Checking what this panel emits means reading far more code than the diff changes, so pre-existing bugs surface on every review. One already on the base branch stays 🟣 however bad it is: this pull request did not cause it, so it cannot be a reason to hold this pull request. Say in one clause that it predates the change. The exception is a live security hole on an exposed surface — still 🟣, but open the summary with it.
internal/web/controller/, session and
middleware code, the PUBLIC internal/sub/ subscription server, and Xray
config generation in internal/xray/.runtime.Runtime
(internal/web/runtime/) and calls internal/xray/api.go directly, or
dispatches from a controller or cron job. It passes every local test and
silently breaks every multi-node deployment.internal/database/db.go, one that behaves differently on SQLite and
PostgreSQL, or one that loses or overwrites operator data on upgrade or
rollback. There are no migration files and no down-migrations.internal/util/link/ + internal/sub/, TS
frontend/src/lib/xray/, TS docs/lib/xray/) diverge from one another..github/workflows/: this repository runs workflows with
secrets against a public fork stream. Untrusted expression interpolation
into run: blocks, broadened permissions, weakened guards, or a job that
executes pull-request code.g.POST/g.GET in internal/web/controller/ needs the whole chain:
an entry in frontend/src/pages/api-docs/endpoints.ts, regenerated
artefacts (make gen), any new API-boundary struct added to StructAllow
in tools/openapigen/main.go, and frontend/public/openapi.json copied to
docs/public/openapi.json with the docs MDX regenerated
(cd docs && pnpm gen:api). CI checks the first three; the docs copy is
checked by nothing — a missed copy is Important, not a nit.err != nil or
len(x) > 0, or going green by regenerating golden fixtures or Vitest
snapshots.testing
(never testify), the panel is Ant Design (never Tailwind or shadcn). Neither
golangci-lint nor oxlint forbids the import, so it passes CI clean.npm audit --omit=dev --audit-level=high.
A dev-dependency advisory is out of scope on purpose: it ships to nobody.frontend/src/generated/,
frontend/public/openapi.json, docs/public/openapi.json) or lock files.
Those files being STALE after a source change is reportable; their style
is not.CLAUDE.md rejects such tests outright.frontend/src/test/i18n-dead-keys.test.ts pins the 13 locale files in
internal/web/translation/ in both directions, so the frontend job is
already red. Report the failing check, not the key.Everything named under "What Important means here" gets full scrutiny. Two areas do not — they earn review, but report there only what you are near-certain about and that actually breaks something:
docs/ — the standalone Fumadocs site, with its own CI and its own
dependency tree. docs/lib/xray/ is the exception and gets full scrutiny:
it is the third link implementation.internal/web/translation/ — the key set is CI's job and the wording of a
translation is nobody's here.file:line citation from this repository,
not an inference from a name.postgres-durable-first runs against PostgreSQL, go-test and race are
SQLite, and XRAY_E2E_BINARY and XUI_SCALE_TEST are set by no job, so
those tests have never run in CI at all. Where a change touches dialect,
migration or Xray gRPC code that no job exercised, say it is unverified
rather than repeating a green tick as proof.🔴 findings are never capped. Report every one.
Report at most five 🟡 nits and at most three 🟣 pre-existing bugs. Past that, say "plus N similar" in the summary instead of posting them.
A cap decides WHICH ones survive, so choose rather than truncate: the same nit repeated across files is ONE finding with a count, not five slots; a nit in code this pull request wrote outranks one in code it only moved; and a nit nobody would act on does not deserve a slot at all.
After the first review of a pull request, report 🔴 findings only: a one-line fix must not reach round seven on style.
Open with a one-line tally — 2 🔴 / 4 🟡 / 1 🟣 — so the author sees the
shape of the review before the detail. When nothing is 🔴, lead with
No blocking issues and put the tally after it.
The posted comment is the only part of a review anyone sees, so a bare "no issues found" is a receipt, not a review: nothing in it says whether the diff was read or the run died early. Every comment therefore ends with a short coverage list — one line per area actually checked, naming what was examined and what it turned out to be, plus the head SHA and the size of the diff it covers. Say which claims could not be verified and why, including a check this environment blocked. Keep that coverage list under ten lines; it is evidence, not a retelling of the pull request.