Skip to content

Configuration

All config is environment variables, validated at startup — vibecheck refuses to boot with a missing required var or an insecure VIBECHECK_TOKEN, rather than failing confusingly later. See .env.example for the full annotated list; the ones you'll actually touch:

Renamed from GRUMPY_*

VIBECHECK_BASE_URL, VIBECHECK_TOKEN and VIBECHECK_ALLOWED_REPOS used to be GRUMPY_BASE_URL, GRUMPY_TOKEN and GRUMPY_ALLOWED_REPOS. The old names still work as a fallback (the new name wins if both are set), but will be removed in a later release.

Variable Required Default What it does
DATABASE_URL yes — Postgres connection string
VIBECHECK_BASE_URL yes — Public URL used to build session links. Never derived from request headers, so it works correctly behind a proxy
VIBECHECK_TOKEN yes — Shared bearer secret gating POST /sessions and GET /verdict. Rejected at startup if it's the placeholder or under 20 characters
FAKE_GRADER yes — true = free/instant grading via a looks-good marker string (good for trying vibecheck out); false = real grading via Claude
MODEL_API_KEY only if FAKE_GRADER=false — Anthropic API key used by the real grader
GITHUB_STATUS_TOKEN no unset GitHub token with Commit statuses: Read and write on the gated repos (see Blocking PRs). vibecheck uses it to post the vibecheck/verdict status; unset, it posts nothing and your workflow has to gate on GET /verdict itself. A 401 from GitHub in the logs means the token is wrong or expired
VIBECHECK_ALLOWED_REPOS no unset (any repo) Comma-separated owner/name allow-list. Defense-in-depth if VIBECHECK_TOKEN ever leaks
EXAM_QUESTION_COUNT no 5 Maximum questions in the walkthrough, including the fixed high-level first one. The model writes the rest, each anchored to a part of the diff and shown on its own screen. Small changes get fewer: one scoped question per 15 changed lines, so a one-line PR gets only the high-level question. PASSINGMARKS is clamped to the sheet length
PASSINGMARKS no 3 How many screens must be answered correctly to pass. The session fails as soon as that is out of reach. Must be between 1 and EXAM_QUESTION_COUNT
MAX_QUESTION_ATTEMPTS no 3 Tries per question before vibecheck reveals the answer and moves on. A revealed question counts for nothing. 1 = one try, no retries
ALLOW_RESTART no false Show a Start over button on failed sessions, which resets them to pending with a fresh set of questions on the same commit and link. Unlimited, so turning it on weakens the gate; off by default. See Blocking PRs
MEANIEMODE no false Failure notes become sarcastic and merciless instead of professional. Doesn't change which answers passed, only tone
EVALUATOR no unset (defaults below) JSON config for checks that reject a PR before any question is asked: POST /sessions returns 422 with the reason, and the grill job comments it on the PR and goes red. Today there is one check, size: {"size": {"max_changed_lines": 1000, "exclude": ["*.lock", "package-lock.json", "pnpm-lock.yaml", "go.sum"]}} (those are the defaults). Changed lines are added + removed lines, not context; files matching an exclude glob (path or basename) don't count. max_changed_lines: 0 turns the check off. Omitted keys keep their defaults; unknown keys or bad values fail startup
MAX_DIFF_BYTES no 400000 Larger diffs are rejected like an EVALUATOR check (422, red grill job). Bounded by the model's context window, not by Postgres: at ~3–4 bytes per token, 400 KB is ~100k–130k tokens. Raise it much further and you accept diffs that can never be graded
SESSION_TTL_DAYS no 7 How long a question link stays answerable. After that it shows "This session expired" and the vibecheck/verdict status stays pending; pushing a new commit creates a fresh session