Skip to content
Open
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
22 changes: 22 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@ the self-hosting playbook live in the private `peetzweg/devops` repo.
- **Ambient `GITHUB_TOKEN` in the shell shadows `.env`** and lacks `read:org` — prefix
dev/bun script runs with `env -u GITHUB_TOKEN` when org lookups misbehave.

## Background jobs

Recipe, proven by `monthly-user-refresh` and `backfill-orgs`: a CLI in `scripts/` (flags,
each mirrored by an env var) → bundled by `build:worker` into `.output/worker/<job>.mjs` →
the Dockerfile already copies `.output`, so shipping a new job needs no Dockerfile change →
a Coolify **scheduled task** runs `node .output/worker/<job>.mjs` (cron + `docker exec` into
the running app container; container TZ is UTC). Logic goes in a pure engine
(`src/lib/<job>.ts`) over an injected store (`<job>-store.ts`) so it unit-tests against fakes.

Every job must: take its advisory lock from `LOCK_KEYS` in `src/lib/job-runner.ts` (unique key
per job); use `createBudgetGuard` so it can't drain the GitHub token below the floor that live
traffic needs; stop cleanly on a wall-clock cap instead of being killed; resume off a DB
freshness marker (**the missing row is the retry queue** — no job-state table); write only
idempotent upserts; isolate per-item failures to a narrow status allowlist; and emit one
greppable `<job> done status=… ` summary line. Stagger schedules: the prod box has no swap and
`docker exec` adds a second node process inside the app container.

Two failure modes worth knowing: cleanup must release the lock **first and defensively**, or a
crash mid-teardown leaves it held by a server-side session that outlives the process and every
later run reports `status=locked` and silently does nothing; and never let pool shutdown throw,
or a completed run shows up as a failed Coolify execution.

## Domain guardrails

- Single-segment paths are GitHub logins (`$user` route); editorial pages live under the
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"generate-routes": "tsr generate",
"og": "tsx scripts/generate-og.ts",
"build": "tsx scripts/generate-og.ts && vite build && pnpm run build:worker",
"build:worker": "esbuild scripts/monthly-user-refresh.ts --bundle --platform=node --target=node24 --format=esm --outfile=.output/worker/monthly-user-refresh.mjs",
"build:worker": "esbuild scripts/monthly-user-refresh.ts scripts/backfill-orgs.ts --bundle --platform=node --target=node24 --format=esm --outdir=.output/worker --out-extension:.js=.mjs",
"start": "node .output/server/index.mjs",
"preview": "vite preview",
"test": "vitest run",
Expand All @@ -23,6 +23,7 @@
"refresh-user": "bun scripts/refresh-user.ts",
"monthly:user": "node --env-file-if-exists=.env --import tsx scripts/monthly-user-refresh.ts",
"backfill": "bun scripts/backfill-contributions.ts",
"backfill:orgs": "node --env-file-if-exists=.env --import tsx scripts/backfill-orgs.ts",
"backup": "bash scripts/backup.sh",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
Expand Down
Loading