AI Code Review (scripts/ai-review)
Automated PR review that posts a single consolidated comment on every pull request. Driven by
.github/workflows/ai-code-review.yml → run-review.mjs, using DeepSeek-V4-Pro via opencode.
The posted comment is branded OpenObserve Code Reviewer. Branding is cosmetic + identity only — it does not change what the reviewer finds or decides.
The posted comment never names the model or provider behind the review — the heading carries no
model label, the comment marker is provider-neutral, and redactProviderIdentity in
run-review.mjs strips the provider/model ids out of anything on its way to the PR (a model
naming itself, or an upstream error echoing the slug). Naming the model in this repo and in the
Actions logs is fine; naming it in the comment is not.
How it works (one paragraph)
On each PR the workflow fetches the diff, filters noise, picks a risk tier (trivial / lite /
full), runs the tier's read-only specialist agents (security, code-quality, performance,
documentation, release) in parallel — plus any conditional agent whose domain the diff touches —
and a coordinator consolidates their findings into one comment with an approve/needs-work
decision. The comment PATCHes itself in place on re-review. Full engine details live in the code;
the rest of this README covers setup, activation, and rollback of the branded identity.
The Frontend UI Reviewer
frontend is one of the conditional agents (see qa below): it is appended to whatever the
tier selected whenever the diff touches web/src (excluding specs), so a 6-line UI change still
gets reviewed even at the trivial tier. requiresFocus: true is the other half of that contract — the generic focus filter
falls back to the full diff when an agent matches no files, which would otherwise have the
frontend reviewer opining on Rust; with the flag set it skips the PR entirely instead.
Its remit is deliberately narrow: only the half of .claude/skills/ui-architect/ that no lint
rule or check script can see — structure (OPageHeader/OPageLayout/OTable vs hand-rolled
markup), cross-file consistency (a sortable column vs the Rust handler's sort keys; route ↔ nav
entry ↔ rail gate agreement; a token in :root but not dark.css), values that are well-formed
but wrong (text-[0.8125rem], a parseInt-read px converted to rem), and i18n semantics
(raw() on real copy, a catalogue key for Kafka). Everything local/no-hardcoded-px,
lint:design:strict, stylelint or type-check:app already fails the build on is explicitly
out of scope — see the "What NOT to Flag" table in agents/frontend.md.
The QA / Test Reviewer
qa is a conditional agent on the same contract as frontend, appended whenever the diff touches
the Playwright E2E test code under tests/ui-testing/ — a spec or helper under playwright-tests/,
a page object under pages/, a util under utils/ (.js/.ts), or a playwright*.config. Committed app fixtures
(fixtures/**) and the other suites under tests/ are deliberately excluded, so the agent never
applies POM/selector rules to sample application code. It is
a static review of the tests themselves — it never runs them, so a green CI run clears none of
its findings. It folds in the Council's Sentinel rules (POM compliance incl. selectors inside
expect(...), testLogger not console.log, real assertions — no expect(true).toBe(true),
awaited async, no hardcoded creds) plus the Test Inspector's highest-value check: data-test ↔
component selector cross-validation (grep each added selector across web/src and flag any the
test relies on that no component renders — the "19 broken selectors, 14/14 green" failure mode).
Actually executing the suite stays with playwright.yml; this agent only reads.
Adding another conditional agent: define it in AGENTS (with requiresFocus if it is
domain-scoped), add a { agent, matches } entry to CONDITIONAL_AGENTS, register
ai-review-<key> in opencode.jsonc with the same read-only tool policy, and add its glyph to
CATEGORY_GLYPH and to agents/coordinator.md.
Identity: how the comment gets its author
The comment is posted with whatever token run-review.mjs sees as GH_TOKEN:
- Branded — a GitHub App installation token (comment authored by OpenObserve Code Reviewer with its avatar).
- Fallback —
github.token(comment authored bygithub-actions[bot]).
The workflow mints the App token via actions/create-github-app-token, gated on the
OO_REVIEWER_APP_ID repo/org variable:
- id: app_token
if: ${{ steps.skip.outputs.skip_review == 'false' && vars.OO_REVIEWER_APP_ID != '' }}
continue-on-error: true # never breaks CI if minting fails
uses: actions/create-github-app-token@<pinned-sha> # v2.2.2
with:
app-id: ${{ vars.OO_REVIEWER_APP_ID }}
private-key: ${{ secrets.OO_REVIEWER_APP_PRIVATE_KEY }}
# ...
env:
GH_TOKEN: ${{ steps.app_token.outputs.token || github.token }}
This is fail-safe at every stage:
| Situation | What happens |
|---|---|
| Variable unset | Token step skipped → posts as github-actions[bot] |
| Variable set, App not installed / key wrong | Mint fails, continue-on-error → empty token → || github.token → posts as github-actions[bot] |
| Variable set, App installed | Mint succeeds → posts as OpenObserve Code Reviewer |
Provisioning the GitHub App (one-time)
- Create a GitHub App (org-owned preferred; personal works but must be set to
Any account to install on org repos). Name:
OpenObserve Code Reviewer; upload the brand logo as the avatar; uncheck Webhook → Active (no server, no events). - Permissions (minimum): Pull requests Read & write, Contents Read-only, Metadata Read-only. Do NOT grant Contents write — the review agents are read-only by design and the token must not be write-capable.
- Install the App on
openobserveando2-enterprise. - Generate a private key (
.pem) and note the numeric App ID. - Set on both repos (or at org level), under Settings → Secrets and variables → Actions:
- Variable
OO_REVIEWER_APP_ID= the App ID (this is the on/off gate). - Secret
OO_REVIEWER_APP_PRIVATE_KEY= full contents of the.pem.
- Variable
Via CLI:
gh variable set OO_REVIEWER_APP_ID --body "<APP_ID>" --repo openobserve/openobserve
gh secret set OO_REVIEWER_APP_PRIVATE_KEY < <key.pem> --repo openobserve/openobserve
gh variable set OO_REVIEWER_APP_ID --body "<APP_ID>" --repo openobserve/o2-enterprise
gh secret set OO_REVIEWER_APP_PRIVATE_KEY < <key.pem> --repo openobserve/o2-enterprise
Rollback
Delete the OO_REVIEWER_APP_ID variable on the repo(s). The next review instantly reverts to
github-actions[bot]. No code change, no revert needed.
Skipping a review
Put any of these in the PR body or a comment: [skip ai review], [skip review], [skip ci],
or break glass.
Notes
actions/create-github-app-tokenis pinned to a full commit SHA (it receives the App's private key). Bump the pin deliberately after review — same discipline as theOPENCODE_VERSIONpin.- The App installation's granted permissions bound the token; keep the install at the minimum above so a mis-scoped install can't hand the reviewer a write-capable credential.
- OSS (
openobserve) and ENT (o2-enterprise) run identical engine files — keep them in sync.
Trace content and GenAI attributes
The review exports model instructions, input, and output to the configured OpenObserve trace stream. These values can contain repository source and PR context, so access to that stream must be restricted accordingly.
Model spans use the OpenTelemetry GenAI content attributes:
gen_ai.system_instructionsgen_ai.input.messagesgen_ai.output.messages
Token counts, cost, model, response ID, and finish reasons come from the Opencode assistant
response. Do not put content-length metadata below gen_ai.prompt.*: OpenObserve recognizes that
prefix as legacy TraceLoop prompt content and would tokenize the metadata instead of the prompt.