Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions apps/loopover-ui/content/docs/self-hosting-rees.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ REES_URL=http://rees:8080
REES_SHARED_SECRET=<generate-a-new-shared-secret>`}
/>

No `SENTRY_*` variables are required for a working local REES. Set them only if you want REES
No `POSTHOG_*` variables are required for a working local REES. Set them only if you want REES
error reporting — see "Service configuration" below for the variables REES reads, and add them
for the `rees` service through a `docker-compose.override.yml` rather than the root `.env`: REES
reads the same `SENTRY_DSN` name the main engine uses, so forwarding the whole `.env` file would
point REES's error reporting at the engine's Sentry project instead of a dedicated one.
reads the same `POSTHOG_API_KEY` name the main engine uses, so forwarding the whole `.env` file
would point REES's error reporting at the engine's PostHog project instead of a dedicated one.

### Pointing at an external or managed instance instead

Expand Down Expand Up @@ -177,15 +177,14 @@ when the REES service is inside your trust boundary.

## Service configuration

The REES service must use the matching `REES_SHARED_SECRET`. Optional Sentry env captures
The REES service must use the matching `REES_SHARED_SECRET`. Optional PostHog env captures
analyzer degradations without logging request bodies, tokens, diffs, or review content.

<CodeBlock
filename="REES service env"
code={`REES_SHARED_SECRET=<shared-secret>
SENTRY_DSN=
SENTRY_ENVIRONMENT=production
SENTRY_TRACES_SAMPLE_RATE=0`}
POSTHOG_API_KEY=
POSTHOG_ENVIRONMENT=production`}
/>

## Failure behavior
Expand Down
7 changes: 7 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ ignore:
# Self-host process entry + build-time stubs: exercised by the Docker build+boot smoke test
# (.github/workflows/selfhost.yml), not unit-coverable without booting a server/subprocess.
- "src/server.ts"
# review-enrichment's process entrypoint (#1473) -- same "Docker build+boot, not unit-coverable" reasoning
# as src/server.ts above; module-scope serve() binds a real port as a side effect of importing this file,
# so it's never imported by test/**. Pre-existing gap: this entry was missing even though
# discovery-index/src/server.ts's own header comment already claimed review-enrichment/src/server.ts got
# "the same treatment" -- surfaced by #8290 finally touching enough of this file's lines to trip
# codecov/patch on it.
- "review-enrichment/src/server.ts"
- "src/selfhost/cf-workers-shim.ts"
- "src/selfhost/stubs/**"
# Postgres runtime adapters: validated by the real-Postgres integration test (test/integration/selfhost-pg.ts,
Expand Down
10 changes: 5 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -652,18 +652,18 @@ services:
# (src/review/enrichment-wire.ts) -- this whole service is optional end to end, same as browserless above.
# After `docker compose --profile rees up -d`, set in .env: LOOPOVER_REVIEW_ENRICHMENT=true,
# REES_URL=http://rees:8080, and a freshly generated REES_SHARED_SECRET -- do not reuse a secret from any
# other REES instance (e.g. a managed Railway deployment) you also point at (#1667).
# other REES instance you also point at (#1667).
rees:
build:
context: review-enrichment
restart: unless-stopped
<<: *default-logging
profiles: ["rees"]
# Deliberately NOT `env_file: .env` like the loopover service above: that would also hand this
# analyzer service every unrelated secret in .env, and REES reads the same SENTRY_DSN key name the main
# app uses (src/selfhost/sentry.ts) -- blanket-forwarding .env would silently point REES's error reporting
# at the main app's Sentry project instead of a dedicated one. Add REES-specific Sentry vars via
# docker-compose.override.yml if you want REES error reporting (see review-enrichment/README.md).
# analyzer service every unrelated secret in .env, and REES reads the same POSTHOG_API_KEY key name the
# main app uses (src/selfhost/posthog.ts) -- blanket-forwarding .env would silently point REES's error
# reporting at the main app's PostHog project instead of a dedicated one. Add REES-specific PostHog vars
# via docker-compose.override.yml if you want REES error reporting (see review-enrichment/README.md).
environment:
PORT: "8080"
# Soft default, not ":?required" -- compose interpolates every service's env vars for the whole file
Expand Down
5 changes: 3 additions & 2 deletions review-enrichment/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# LoopOver review-enrichment service (REES). Lean two-stage Node build; analyzers are pure-JS diff-hunk
# heuristics with no external CLI tools (#1477 scoped this to the cheap, no-checkout path -- see
# review-enrichment/src/analyzers/complexity.ts's header for why a real linter/AST toolchain is out of scope).
# Build context = the review-enrichment/ directory (Railway "Root Directory" = review-enrichment).
# Build context = the review-enrichment/ directory ("Root Directory" = review-enrichment for any
# Dockerfile-builder platform).
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
Expand All @@ -27,5 +28,5 @@ USER rees
ENV PORT=8080
EXPOSE 8080
# Provide at runtime (NOT baked into the image): REES_SHARED_SECRET (shared bearer with the engine),
# plus optional Sentry vars. The uploader no-ops unless Sentry auth/org/project/release are configured.
# plus optional PostHog vars. The uploader no-ops unless POSTHOG_CLI_API_KEY/PROJECT_ID/POSTHOG_RELEASE are configured.
CMD ["sh", "-c", "node dist/upload-sourcemaps.js && find dist -type f -name '*.map' -delete && node dist/server.js"]
129 changes: 59 additions & 70 deletions review-enrichment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A standalone microservice that produces a structured **review brief** for the lo
in-network alongside a self-hosted engine via the repo-root `docker-compose --profile rees` service (the simplest
path, no separate hosting to manage — see the [self-hosting REES docs](https://loopover.ai/docs/self-hosting-rees)),
or deploy it as its own service on any platform that can run a Dockerfile-based Node service — see
[Deploy (Railway)](#deploy-railway) below for one example.
[Deploy standalone](#deploy-standalone) below.

The engine reviews PRs by running a headless `claude --print` subprocess with `Bash`/`WebFetch` disallowed and **no
repo checkout**, so it cannot run a linter, hit a CVE database, resolve a dependency tree, or query git history. REES
Expand Down Expand Up @@ -200,7 +200,7 @@ analyzers should prefer it for shared PR facts instead of reparsing the envelope
Context caches are request-scoped only. They are for avoiding duplicate work inside one enrichment run, not for
cross-request TTL storage. Cache metrics are aggregate and public-safe: hit/miss counts, external-call counts by
category, skipped/capped work counts by category, and elapsed time. Never put request bodies, diffs, prompts,
comments, tokens, private configs, or raw external payloads into cache categories, metric keys, Sentry tags, or logs.
comments, tokens, private configs, or raw external payloads into cache categories, metric keys, PostHog tags, or logs.

The engine also sends `budget.timeoutMs` with one second of headroom below `REES_TIMEOUT_MS`, so REES can return a
partial/degraded brief before the caller aborts the HTTP request. If your REES deployment is still running an older
Expand All @@ -217,68 +217,58 @@ curl -XPOST localhost:8080/v1/enrich -H 'authorization: Bearer dev' \
-H 'content-type: application/json' -d '{"repoFullName":"o/r","prNumber":1}'
```

## Deploy (Railway)
## Deploy standalone

For a self-hosted engine, `docker compose --profile rees up -d` from the repo root (see the
[self-hosting REES docs](https://loopover.ai/docs/self-hosting-rees)) is the simplest path — no
separate service to host. If you'd rather run REES on its own outside that compose network, it's a plain
Dockerfile-based Node service and can go anywhere that builds one; Railway is one option this repo has release
tooling for (the Sentry/source-map wiring below). Point **Root Directory = `review-enrichment`** at a `railway.json`
you add there (see Railway's Dockerfile-builder docs) and set `REES_SHARED_SECRET` (same value the engine holds) as a
service variable — never commit it. The engine reaches the service over Railway **private networking**
(`<service>.railway.internal`); no public domain is required.

## Sentry releases and source maps

REES supports optional Sentry error reporting and source-map upload for Railway deployments. The Docker image builds
`dist/*.js.map` with embedded `sourcesContent`, then the runtime startup command injects Sentry debug ids, uploads the
exact post-injection `dist/` files, records a deploy, removes source maps from the running filesystem, and starts
`dist/server.js`.

Set these Railway service variables:

| Variable | Purpose |
| ------------------------------ | ----------------------------------------------------------------------- |
| `SENTRY_DSN` | Enables REES error capture. Unset means the SDK is a no-op. |
| `SENTRY_AUTH_TOKEN` | Allows the runtime uploader to create releases and upload source maps. |
| `SENTRY_ORG` | Sentry organization slug. |
| `SENTRY_PROJECT` | Sentry project slug. |
| `SENTRY_ENVIRONMENT` | Optional; defaults to Railway's environment name, then `production`. |
| `SENTRY_TRACES_SAMPLE_RATE` | Optional; defaults to `0`, so errors report without tracing. |
| `SENTRY_RELEASE` | Optional override. Only set it when that exact REES bundle is uploaded. |
| `SENTRY_URL` | Optional Sentry API URL; defaults to `https://sentry.io`. |
| `SENTRY_REPOSITORY` | Optional; defaults to `JSONbored/loopover` for commit association. |
| `REES_SENTRY_UPLOAD_STRICT` | Optional. Set `true` to fail startup if source-map upload fails. |
| `REES_SENTRY_VALIDATE_RELEASE` | Optional. Set `false` only to disable post-upload release validation. |

By default the release id is `loopover-rees@<RAILWAY_GIT_COMMIT_SHA>`, using Railway's Git metadata. The Sentry
GitHub code mapping should be:

| Sentry field | Value |
| ---------------- | ------------------- |
| Stack Trace Root | `/app` |
| Source Code Root | `review-enrichment` |
| Branch | `main` |

Do **not** pass `SENTRY_AUTH_TOKEN` as a Docker build arg. Railway deploys this service from Git, and Docker build args
can leak through image metadata. Keeping the upload at runtime means Sentry sees the same `dist/` files that the service
executes, without exposing source maps over HTTP.

After upload, startup validates the exact `loopover-rees@<RAILWAY_GIT_COMMIT_SHA>` release through the Sentry API:
the release must exist, be finalized, include the deployed commit, and include the Railway deploy id/environment. If
`REES_SENTRY_UPLOAD_STRICT=true`, a failed upload or failed validation stops the Railway deployment; otherwise it logs a
`rees_sentry_sourcemap_upload_failed` warning so the problem is visible without blocking startup.

Analyzer failures are still fail-open: the `/v1/enrich` response marks the analyzer as `degraded` and returns a partial
brief. When Sentry is enabled, those degradations are captured as `rees_analyzer_degraded` events with tags/context for
`analyzer`, requested analyzer list, `repo`, `pullNumber`, head SHA prefix, `release`, `environment`, timeout budget,
elapsed time, partial/analyzer status, history lookup counts, GitHub endpoint category, request id, and trace id. Use
those fields to spot a broken analyzer without exposing request bodies, diffs, tokens, prompts, comments, or private
config.

### REES Sentry queries

REES keeps its indexed Sentry tags intentionally small and stable:
Dockerfile-based Node service and can go anywhere that builds one — point your platform's Dockerfile builder at
this directory (**Root Directory = `review-enrichment`**) and set `REES_SHARED_SECRET` (same value the engine
holds) as a runtime env var — never commit it. Reach the service however your platform does private
networking, or over a public domain with the shared secret as the only gate.

## PostHog releases and source maps

REES supports optional PostHog error reporting and source-map upload for standalone deployments. The Docker
image builds `dist/*.js.map` with embedded `sourcesContent`, then the runtime startup command injects PostHog
chunk ids, uploads the exact post-injection `dist/` files, removes source maps from the running filesystem, and
starts `dist/server.js`.

Set these runtime env vars:

| Variable | Purpose |
| -------------------------------- | ---------------------------------------------------------------------------- |
| `POSTHOG_API_KEY` | Enables REES error capture (a PostHog project token). Unset means a no-op. |
| `POSTHOG_HOST` | Optional PostHog ingestion host override (EU region). |
| `POSTHOG_ENVIRONMENT` | Optional; defaults to `production`. |
| `POSTHOG_RELEASE` | Optional override. Only set it when that exact REES bundle is uploaded. |
| `POSTHOG_COMMIT_SHA` | Optional; used to derive `POSTHOG_RELEASE` (`loopover-rees@<sha>`) when unset. |
| `POSTHOG_CLI_API_KEY` | A PostHog **personal** API key (error-tracking write + organization read scopes) -- allows the runtime uploader to upload source maps. Distinct from `POSTHOG_API_KEY`. |
| `POSTHOG_CLI_PROJECT_ID` | The PostHog project's numeric id, required alongside `POSTHOG_CLI_API_KEY`. |
| `POSTHOG_CLI_HOST` | Optional posthog-cli host override (EU region). |
| `REES_POSTHOG_UPLOAD_STRICT` | Optional. Set `true` to fail startup if source-map upload fails. |
| `REES_POSTHOG_VALIDATE_RELEASE` | Optional. Set `false` only to disable post-upload release validation. |

Do **not** pass `POSTHOG_CLI_API_KEY` as a Docker build arg on any platform that deploys this service from Git --
build args can leak through image metadata. Keeping the upload at runtime means PostHog sees the same `dist/`
files that the service executes, without exposing source maps over HTTP.

After upload, startup validates the release by querying PostHog's `error_tracking/symbol_sets` API: at least one
symbol set must exist for the release, with no recorded `failure_reason`. PostHog's release model has no
Sentry-style commits/deploys/finalize lifecycle to check beyond that. If `REES_POSTHOG_UPLOAD_STRICT=true`, a
failed upload or failed validation stops startup; otherwise it logs a `rees_posthog_sourcemap_upload_failed`
warning so the problem is visible without blocking startup.

Analyzer failures are still fail-open: the `/v1/enrich` response marks the analyzer as `degraded` and returns a
partial brief. When PostHog is enabled, those degradations are captured as `rees_analyzer_degraded` events with
properties for `analyzer`, requested analyzer list, `repo`, `pullNumber`, head SHA prefix, `release`,
`environment`, timeout budget, elapsed time, partial/analyzer status, history lookup counts, GitHub endpoint
category, request id, and trace id. Use those fields to spot a broken analyzer without exposing request bodies,
diffs, tokens, prompts, comments, or private config.

### REES PostHog queries

REES keeps its indexed PostHog properties intentionally small and stable:

- `event`
- `route`
Expand All @@ -288,7 +278,7 @@ REES keeps its indexed Sentry tags intentionally small and stable:
- `analyzer`
- `release`
- `environment`
- `railwayDeploymentId`
- `deploymentId`

Useful production queries:

Expand All @@ -297,17 +287,16 @@ Useful production queries:
- Analyzer failures grouped by analyzer:
- `event:rees_analyzer_degraded analyzer:history`
- `event:rees_analyzer_degraded analyzer:dependency repo:JSONbored/loopover`
- Source-map upload/startup failures on a Railway deploy:
- `event:rees_sourcemap_upload_failed railwayDeploymentId:<deploy-id>`
- Source-map upload/startup failures:
- `event:rees_sourcemap_upload_failed`
- Process-level crashes:
- `event:rees_uncaught_exception`
- `event:rees_unhandled_rejection`

If Sentry still shows frames such as `/app/dist/server.js`, check:
If PostHog still shows frames such as `/app/dist/server.js`, check:

1. The event's `release` is `loopover-rees@<same Railway commit sha>` or your exact `SENTRY_RELEASE` override.
2. The Sentry release has an artifact bundle uploaded for the REES project.
3. Railway has `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, and `SENTRY_PROJECT` set on the REES service.
4. Startup logs include `sentry_release_validation_complete` for the same release id and Railway deployment id.
5. The Sentry code mapping is `/app` → `review-enrichment` on branch `main`.
6. `npm --prefix review-enrichment run validate:sourcemaps` passes locally.
1. The event's `release` is `loopover-rees@<same commit sha>` or your exact `POSTHOG_RELEASE` override.
2. The PostHog project has a symbol set uploaded for that release.
3. The deployment has `POSTHOG_CLI_API_KEY` and `POSTHOG_CLI_PROJECT_ID` set.
4. Startup logs include `rees_posthog_release_validation_complete` for the same release id.
5. `npm --prefix review-enrichment run validate:sourcemaps` passes locally.
Loading
Loading