From 7d62579608431595efffe405354ab40efda429af Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 27 Jul 2026 15:25:58 +0200 Subject: [PATCH 1/2] Add start-hackbot skill A Claude Code skill documenting how to start the hackbot services locally: the API (hackbot-api), the web UI (hackbot-ui), and the pulse listener, plus a quick stub-backed UI demo and a recipe for validating alembic migrations against a throwaway Postgres. --- .claude/skills/start-hackbot/SKILL.md | 156 ++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .claude/skills/start-hackbot/SKILL.md diff --git a/.claude/skills/start-hackbot/SKILL.md b/.claude/skills/start-hackbot/SKILL.md new file mode 100644 index 0000000000..b3ad0102bf --- /dev/null +++ b/.claude/skills/start-hackbot/SKILL.md @@ -0,0 +1,156 @@ +--- +name: start-hackbot +description: How to start / run the hackbot services locally — the web UI (hackbot-ui), the API (hackbot-api), and the pulse listener. Use when asked to run, start, launch, boot, or "try" hackbot, the hackbot UI, the hackbot dashboard, or hackbot-api locally. +--- + +# Starting hackbot + +Hackbot is three cooperating services under `services/`: + +| Service | What it is | Runtime | +| --- | --- | --- | +| `hackbot-api` | FastAPI orchestrator; triggers agents as Cloud Run Jobs, stores runs in Cloud SQL Postgres | Python (uvicorn) | +| `hackbot-ui` | Next.js "Launchpad" — trigger runs, browse "Recent runs", read results | Node (Next.js) | +| `hackbot-pulse-listener` | Listens to Firefox CI build failures and triggers `build-repair` runs via the API | Python | + +The UI talks only to the API (server-side, via `X-API-Key`); the API owns the DB, GCS, and Cloud Run Jobs. + +Pick the path that matches the request: + +- **"Try the UI / see the dashboard" without GCP creds** → [Quick local UI demo](#quick-local-ui-demo). Fastest; no Cloud SQL / GCP / Google OAuth needed. +- **Run the real API** → [Run hackbot-api](#run-hackbot-api). Needs GCP + Cloud SQL config. +- **Run the real UI against a real API** → [Run hackbot-ui](#run-hackbot-ui). Needs Google OAuth. +- **Run the pulse listener** → [Run the pulse listener](#run-the-pulse-listener). + +--- + +## Quick local UI demo + +Runs the **real** UI (proxy routes + components) against a throwaway stub that stands in for +hackbot-api, so the "Recent runs" view can be inspected without Cloud SQL / GCP. Auth still +applies — see the note at the end for skipping Google OAuth locally. + +Requirements: `python3`, `node`/`npm`. + +1. **Stub API** — a small stdlib HTTP server that serves `GET /agents`, `GET /runs` + (honoring `agent`/`status`/`author`/`limit`/`offset`), and `GET /runs/{id}` with seeded + fake runs. It lives at `services/hackbot-ui/dev/stub_api.py`; run it on :8080: + + ```bash + python3 services/hackbot-ui/dev/stub_api.py # listens on 127.0.0.1:8080 + ``` + +2. **UI env** — create `services/hackbot-ui/.env.local` (gitignored), pointing at the stub: + + ```bash + HACKBOT_API_URL=http://127.0.0.1:8080 + HACKBOT_API_KEY=local-dev-key + BETTER_AUTH_URL=http://localhost:3000 + BETTER_AUTH_SECRET=local-dev-secret-please-ignore-0123456789abcdef + GOOGLE_CLIENT_ID=... # a Google OAuth client (see hackbot-ui README) + GOOGLE_CLIENT_SECRET=... + ``` + +3. **Run the UI**: + + ```bash + cd services/hackbot-ui && npm install && npm run dev + ``` + + Open http://localhost:3000 and sign in with a `@mozilla.com` Google account. The stub then + backs every run the UI reads (filtering, "My runs" vs "All runs", the author column, etc.). + +**Skipping Google OAuth locally.** The codebase has no auth bypass. If you don't want to +configure an OAuth client for a throwaway demo, add a temporary, env-gated bypass yourself +(and never commit it): short-circuit `middleware.ts` and `getAuthedEmail()` in `lib/session.ts` +when a `DEV_AUTH_EMAIL` env var is set, and fall back to `NEXT_PUBLIC_DEV_AUTH_EMAIL` for the +client identity in `components/RecentRuns.tsx`. Because they key off env vars that are unset in +production, they stay inert there — but keep them out of committed branches. + +Stop: `pkill -f stub_api.py; pkill -f "next dev"`. + +--- + +## Run hackbot-api + +The API connects to **Cloud SQL Postgres via the Google connector** (see +`app/database/connection.py`) and triggers **Cloud Run Jobs** + signs **GCS** URLs, so a +faithful run needs GCP application-default credentials and this config (env or a `.env` in +`services/hackbot-api/`): + +- `GCP_PROJECT`, `GCP_REGION`, `RESULTS_BUCKET` +- `CLOUD_SQL_INSTANCE`, `DB_USER`, `DB_PASS`, `DB_NAME` +- `EXTERNAL_API_KEY` (the `X-API-Key` the UI must match) +- `PHABRICATOR_API_KEY` (32+ chars) and `WEBHOOK_SECRET` — **required at import** (settings + validation fails without them, even for unrelated commands/tests). For local non-webhook + use, dummies are fine (see `tests/conftest.py`). + +Apply DB migrations first (manual — nothing auto-runs them): + +```bash +cd services/hackbot-api +uv run alembic upgrade head # against the configured Cloud SQL instance +``` + +Run the server: + +```bash +cd services/hackbot-api +uv run --package hackbot-api uvicorn app.main:app --host 0.0.0.0 --port 8080 +``` + +Tests (no GCP needed; conftest stubs the required secrets): + +```bash +cd services/hackbot-api && uv run --extra dev pytest -q +``` + +### Validating a migration without touching the real DB + +`alembic` online mode uses the Cloud SQL connector, so to test a migration against a local +throwaway Postgres, generate the offline SQL and apply it: + +```bash +cd services/hackbot-api +docker run -d --name hb-mig -e POSTGRES_PASSWORD=pw -e POSTGRES_DB=hackbot -p 55432:5432 postgres:16 +# temp ini with an absolute script_location + a sqlalchemy.url: +sed -e "s#script_location = %(here)s/alembic#script_location = $PWD/alembic#" \ + -e '/^prepend_sys_path/a sqlalchemy.url = postgresql://postgres:pw@127.0.0.1:55432/hackbot' \ + alembic.ini > /tmp/alembic_local.ini +export PHABRICATOR_API_KEY="api-$(printf 'a%.0s' {1..28})" WEBHOOK_SECRET=x +uv run --package hackbot-api alembic -c /tmp/alembic_local.ini upgrade head --sql > /tmp/up.sql +docker exec -i hb-mig psql -U postgres -d hackbot < /tmp/up.sql +docker rm -f hb-mig +``` + +--- + +## Run hackbot-ui + +Full instructions: `services/hackbot-ui/README.md`. Summary — the real UI requires **Google +OAuth** (@mozilla.com only) and a reachable hackbot-api: + +```bash +cd services/hackbot-ui +cp .env.example .env.local +# fill HACKBOT_API_URL, HACKBOT_API_KEY (== api's EXTERNAL_API_KEY), +# BETTER_AUTH_SECRET (openssl rand -base64 32), GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET +# Google OAuth authorized redirect URI: http://localhost:3000/api/auth/callback/google +npm install && npm run dev # http://localhost:3000 -> /login +``` + +Auth is stateless (no DB); only `BETTER_AUTH_SECRET` must be shared across instances. +To skip OAuth for local work, use the [Quick local UI demo](#quick-local-ui-demo) bypass instead. + +--- + +## Run the pulse listener + +Full instructions: `services/hackbot-pulse-listener/README.md`. Needs Pulse credentials and +points at a running hackbot-api: + +```bash +export PULSE_USER=... PULSE_PASSWORD=... # pulseguardian.mozilla.org +export DRY_RUN=true # log intended calls, don't POST +uv run --package hackbot-pulse-listener python -m app +``` From 4bdba0da6b60bf1b9a4e5a9e5399ab90d56e9391 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 27 Jul 2026 16:37:00 +0200 Subject: [PATCH 2/2] Address review on the start-hackbot skill - Drop the auth-bypass recipe: point at registering a throwaway OAuth client instead, so nothing here nudges anyone toward shipping a bypass. - Stop claiming the quick demo needs no Google OAuth; it does. - Describe the demo in terms of what the stub actually serves (read-only GETs), and note HACKBOT_DEV_USER plus what to do if the stub isn't in the checkout. - Replace the broad pkill with Ctrl+C, and use {run_id} for the path param. - Run prettier over the file. --- .claude/skills/start-hackbot/SKILL.md | 45 +++++++++++++++------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/.claude/skills/start-hackbot/SKILL.md b/.claude/skills/start-hackbot/SKILL.md index b3ad0102bf..c9c8f40658 100644 --- a/.claude/skills/start-hackbot/SKILL.md +++ b/.claude/skills/start-hackbot/SKILL.md @@ -7,17 +7,17 @@ description: How to start / run the hackbot services locally — the web UI (hac Hackbot is three cooperating services under `services/`: -| Service | What it is | Runtime | -| --- | --- | --- | -| `hackbot-api` | FastAPI orchestrator; triggers agents as Cloud Run Jobs, stores runs in Cloud SQL Postgres | Python (uvicorn) | -| `hackbot-ui` | Next.js "Launchpad" — trigger runs, browse "Recent runs", read results | Node (Next.js) | -| `hackbot-pulse-listener` | Listens to Firefox CI build failures and triggers `build-repair` runs via the API | Python | +| Service | What it is | Runtime | +| ------------------------ | ------------------------------------------------------------------------------------------ | ---------------- | +| `hackbot-api` | FastAPI orchestrator; triggers agents as Cloud Run Jobs, stores runs in Cloud SQL Postgres | Python (uvicorn) | +| `hackbot-ui` | Next.js "Launchpad" — trigger runs, browse "Recent runs", read results | Node (Next.js) | +| `hackbot-pulse-listener` | Listens to Firefox CI build failures and triggers `build-repair` runs via the API | Python | The UI talks only to the API (server-side, via `X-API-Key`); the API owns the DB, GCS, and Cloud Run Jobs. Pick the path that matches the request: -- **"Try the UI / see the dashboard" without GCP creds** → [Quick local UI demo](#quick-local-ui-demo). Fastest; no Cloud SQL / GCP / Google OAuth needed. +- **"Try the UI / see the dashboard" without GCP creds** → [Quick local UI demo](#quick-local-ui-demo). Fastest; no Cloud SQL / GCP needed (a Google OAuth client still is — sign-in always applies). - **Run the real API** → [Run hackbot-api](#run-hackbot-api). Needs GCP + Cloud SQL config. - **Run the real UI against a real API** → [Run hackbot-ui](#run-hackbot-ui). Needs Google OAuth. - **Run the pulse listener** → [Run the pulse listener](#run-the-pulse-listener). @@ -27,19 +27,24 @@ Pick the path that matches the request: ## Quick local UI demo Runs the **real** UI (proxy routes + components) against a throwaway stub that stands in for -hackbot-api, so the "Recent runs" view can be inspected without Cloud SQL / GCP. Auth still -applies — see the note at the end for skipping Google OAuth locally. +hackbot-api, so the "Recent runs" view can be inspected without Cloud SQL / GCP. **Sign-in +still applies**: there is no auth bypass, so this path needs a Google OAuth client (a personal +throwaway one is fine — see `services/hackbot-ui/README.md`). Requirements: `python3`, `node`/`npm`. 1. **Stub API** — a small stdlib HTTP server that serves `GET /agents`, `GET /runs` - (honoring `agent`/`status`/`author`/`limit`/`offset`), and `GET /runs/{id}` with seeded + (honoring `agent`/`status`/`author`/`limit`/`offset`), and `GET /runs/{run_id}` with seeded fake runs. It lives at `services/hackbot-ui/dev/stub_api.py`; run it on :8080: ```bash - python3 services/hackbot-ui/dev/stub_api.py # listens on 127.0.0.1:8080 + # Optional: attribute the seeded "my runs" to the account you sign in with. + HACKBOT_DEV_USER=you@mozilla.com python3 services/hackbot-ui/dev/stub_api.py # 127.0.0.1:8080 ``` + If that file isn't in the checkout, the stub hasn't landed there yet — use + [Run hackbot-api](#run-hackbot-api) instead. + 2. **UI env** — create `services/hackbot-ui/.env.local` (gitignored), pointing at the stub: ```bash @@ -58,16 +63,14 @@ Requirements: `python3`, `node`/`npm`. ``` Open http://localhost:3000 and sign in with a `@mozilla.com` Google account. The stub then - backs every run the UI reads (filtering, "My runs" vs "All runs", the author column, etc.). + backs every run the UI reads, so the whole "Recent runs" surface — filters, paging, run + detail — works against seeded data. -**Skipping Google OAuth locally.** The codebase has no auth bypass. If you don't want to -configure an OAuth client for a throwaway demo, add a temporary, env-gated bypass yourself -(and never commit it): short-circuit `middleware.ts` and `getAuthedEmail()` in `lib/session.ts` -when a `DEV_AUTH_EMAIL` env var is set, and fall back to `NEXT_PUBLIC_DEV_AUTH_EMAIL` for the -client identity in `components/RecentRuns.tsx`. Because they key off env vars that are unset in -production, they stay inert there — but keep them out of committed branches. +Note that the stub is read-only: triggering a run from the form will fail, since it implements +only the `GET` endpoints the dashboard reads. -Stop: `pkill -f stub_api.py; pkill -f "next dev"`. +Stop both processes with Ctrl+C in the terminals running them (avoid `pkill -f "next dev"`, +which would also kill unrelated Next.js dev servers). --- @@ -139,8 +142,10 @@ cp .env.example .env.local npm install && npm run dev # http://localhost:3000 -> /login ``` -Auth is stateless (no DB); only `BETTER_AUTH_SECRET` must be shared across instances. -To skip OAuth for local work, use the [Quick local UI demo](#quick-local-ui-demo) bypass instead. +Auth is stateless (no DB); only `BETTER_AUTH_SECRET` must be shared across instances. There is +no auth bypass, and adding one isn't worth the risk — register a throwaway OAuth client for +local work. If you only need to look at the dashboard, the +[Quick local UI demo](#quick-local-ui-demo) avoids needing a real API. ---