Thanks for thinking about contributing to codex-lb! This document covers everything you need to get from "clone" to "merged PR" without re-discovering the conventions yourself.
If anything below is unclear or out of date, please open a small docs: PR —
that's the easiest first contribution.
- Code of conduct
- Ways to contribute
- Development setup
- Project layout
- Workflow: OpenSpec-first
- Coding conventions
- Commit & PR conventions
- Merge gates and collaborator rules
- Tests
- Release process
- Security issues
Participation in this project is governed by the Contributor Covenant Code of Conduct. By participating, you agree to uphold it.
You don't need to write code to help:
- Triage: reproduce open issues, ask for missing details, add labels.
- Docs: improve the README, fix typos, document a deployment recipe.
- Discussions: answer "how do I…?" questions in GitHub Discussions.
- Bug reports: file a structured report using the Bug Report issue form.
- Feature ideas: propose them in Discussions first if open-ended; use the Feature Request form when the idea is concrete.
- Code: fix a bug, implement a feature, refactor a hot spot.
Look for issues labelled
good first issue or
help wanted
if you're looking for a starter task.
codex-lb is a Python project managed with uv.
# 1. Fork & clone
git clone https://github.com/<your-user>/codex-lb.git
cd codex-lb
# 2. Install Python + deps via uv
uv sync --all-extras --dev
# 3. Activate the venv (optional — `uv run` works without activating)
source .venv/bin/activate
# 4. Install pre-commit hooks
uv run pre-commit install
# 5. Run the proxy locally
uv run codex-lb --helpFrontend (dashboard) lives under frontend/. Use the project's standard
package manager (see frontend/package.json).
.
├── app/ # Python application (proxy, accounts, dashboard backend)
├── frontend/ # Dashboard SPA
├── deploy/helm/codex-lb/ # Helm chart
├── tests/
│ ├── unit/ # Fast, isolated tests
│ └── integration/ # End-to-end / network-touching tests
├── openspec/ # SSOT — specs, change proposals, archived changes
│ ├── specs/<capability>/
│ └── changes/<change>/
└── .github/ # Issue forms, PR template, workflows
codex-lb uses OpenSpec as the source of truth for change-driven development. You don't have to author OpenSpec entries for trivial fixes, but for anything that changes observable behavior, requirements, contracts, or schema, an OpenSpec change is expected.
The typical flow:
- Find the relevant spec in
openspec/specs/<capability>/spec.md. - If your change alters behavior: create
openspec/changes/<kebab-case-change>/with a proposal + tasks. - Implement the tasks; keep
spec.mdin sync. - Validate:
openspec validate --specs. - Verify and archive when done.
If you're unsure whether your PR needs an OpenSpec change, open it as draft and ask — a maintainer will tell you.
Do not hand-edit CHANGELOG.md. Release notes are generated by
release-please from your commit titles.
- Python: 3.13+. Code is formatted with
ruff format, linted withruff check, and type-checked withty(Astral's type checker). - Imports / style: follow what you see in nearby files; the linter is
authoritative — if
ruffand your style guide disagree, ruff wins. - Type hints: required on new public functions and new modules. Existing code is being typed incrementally.
- Logging: prefer the project's structured logger over
print. - Secrets: never commit API keys, tokens, account credentials, or
.envfiles. The CI redaction is best-effort, not a safety net.
Run the full lint/test gate locally before pushing:
uv run pre-commit run local-ci --hook-stage manual --all-filesThe local-ci hook runs make ci, which is the local
version of the GitHub Actions CI gate. You can also run a single CI job while
iterating, for example:
make lint
make test-unit
make packagecodex-lb uses Conventional Commits because release-please derives the next version + changelog from them.
Format:
<type>(<scope>)?: <subject>
[optional body]
[optional footer(s)]
Common types:
| Type | When to use it |
|---|---|
fix |
Bug fix → patch version bump |
feat |
New user-facing feature → minor version bump |
refactor |
Internal change, no behavior change |
perf |
Performance improvement |
docs |
Documentation only |
test |
Test-only change |
build |
Build system / packaging |
ci |
CI configuration |
chore |
Tooling / maintenance with no user-visible effect |
Common scopes used in this repo: proxy, accounts, auth, oauth,
quota, chat, proxy-responses, dashboard-auth, ui, db, types,
openspec, main (release).
Breaking changes: append ! after the type/scope and/or add a
BREAKING CHANGE: footer.
Examples (from the actual repo history):
fix(proxy): handle model fetch timeouts
fix(accounts): recover quota status from usage refresh
feat(proxy): add OpenAI-compatible /v1/images API (gpt-image-2 via image_generation tool)
fix(dashboard-auth): reject passwords longer than bcrypt's 72-byte limit
PR titles must follow the same format — that's the title release-please reads.
- Create a branch from
main:git checkout -b <type>/<short-name>. - Make atomic commits with Conventional Commit titles.
- Run the lint/test gate locally (see above).
- Open a PR using the template. Link the relevant issue.
- CodeRabbit (and a human maintainer) will review. Address feedback by pushing follow-up commits — no force-pushing during active review.
- Once approved and CI is green, a maintainer squash-merges with a clean Conventional Commits title.
These rules apply to every PR, regardless of author (external contributor, project owner, or collaborator). They formalize the review bar that has produced the current proxy / continuity-recovery / OpenSpec quality so far.
Before a PR is squash-merged into main:
- CI must be all-green on the merge-target head. "UNSTABLE, looks
fine" is not a green CI; rerun, fix, or wait. The Helm / migration /
PostgreSQL test jobs are part of the gate, not optional. The
CI Requiredcheck is the branch-protection check to require: it depends on every CI job and also runs for merge queue synthetic merge groups, so a stale PR head cannot bypass a broken merge result. - Actionable CodeRabbit findings must be fixed or explicitly addressed
or dismissed in-thread on the merge-target head. Review the current-head
CodeRabbit findings before merging; no finding may be silently skipped.
Local
codex review --base origin/mainruns remain an encouraged extra tool, but they are not a merge gate and do not substitute for CodeRabbit.- P1 findings: fix in the PR, or justify in-thread with a short write-up of why the finding doesn't apply. No silent skipping.
- P2 findings: fix in the PR, or open a follow-up issue and link it in the PR thread before merging.
mergeablemust beCLEANin the GitHub API — no merge conflicts, no requested-changes review still outstanding, no missing required status check.- OpenSpec change folder for behavior changes (see
Workflow: OpenSpec-first). Pure
refactors, docs-only edits, dev-tool changes, and test stabilization
PRs are exempt; everything else needs an
openspec/changes/<slug>/entry. Fixes #N/Closes #Nin the PR body for anything that resolves an issue, so the issue close stays automatic and the merge stays traceable. UseRefs #N/Related to #Nfor partial cover.- Simplicity gates must pass (see
Simplicity gates): the five simplicity rules
(PRINCIPLES.md P1-P5). Budget exceptions need the
maintainer-applied
simplicity-budget-approvedlabel.
These implement PRINCIPLES.md; the normative spec is
openspec/specs/contribution-simplicity/spec.md (created when the
codify-simplicity-principles change is archived). Reviewers apply them to
every PR (budget checks are enforced by CI as of the
ci-simplicity-budgets change; reviewer-enforced before that):
- New features default to off or zero-config. No new required
setup step (env var, migration action, external account, manual
file edit) on the base install path without maintainer approval via
the
simplicity-budget-approvedlabel. - Every new
CODEX_LB_*setting justifies not being a default. The PR body answers "why can't this be a hardcoded default?" for each new setting; internals-only knobs stay out of.env.example. - README,
.env.example, and dashboard nav are budgeted. The caps live in.github/simplicity-budgets.toml(introduced by theci-simplicity-budgetschange; until that manifest exists onmain, reviewers judge growth of these surfaces directionally rather than against numeric caps). Exceeding a cap requires the maintainer-appliedsimplicity-budget-approvedlabel before merge. - Feature docs go to
docs/+ OpenSpec, never new README sections. Each spec-governed docs page links back to itsopenspec/specs/<capability>/entry. - Dashboard-visible PRs include before/after screenshots (or a short recording) in the PR body.
Collaborators (write-access contributors) follow two additional rules on top of the merge gates above:
- No self-merge by default. A collaborator's own PR is merged by another maintainer (or, until the project has more collaborators, by the project owner). Review independence matters more than turnaround.
- Large PRs get split. Roughly:
- If a PR is a stack tip pulling in unrelated commits from sibling branches, split it so each merged PR is a single scoped change.
- If a single PR is over ~800 net lines and spans multiple concerns, split it into reviewable pieces. A single 1500-line change scoped to one capability is fine; a 400-line change that touches the proxy hot path and the dashboard and the OAuth flow is not.
To keep the project unblocked if the owner is unavailable, the following self-merge escape hatch applies:
- If a collaborator's PR has been waiting on a maintainer merge for
more than 14 days with all merge gates met (CI green,
CodeRabbit findings addressed,
mergeable=CLEAN, no outstanding requested-changes review, no objection from any other active collaborator in the thread), the PR author may self-merge. - Self-merge under this clause must include a comment on the PR explicitly invoking the clause and linking to the date the merge gates first went green. Audit trail must stay clean.
- The clause is a safety valve, not a default path. If you're tempted to invoke it on a PR you opened less than two weeks ago, the merge gates probably aren't actually all green yet.
These rules are intentionally lightweight. They don't require:
- A second human reviewer in addition to CodeRabbit for every PR. CodeRabbit review + the PR author + a maintainer merge is the baseline.
- Squash-merge commit message rewriting beyond the Conventional Commits title. The PR description ends up in the body; that's enough.
- A formal escalation process for disagreements. If a P1 finding is disputed, work it out in-thread; if it can't be resolved, leave the PR open and ping the owner.
- Unit tests live under
tests/unit/and should be fast and hermetic. - Integration tests live under
tests/integration/and may spin up the app, hit local sockets, or use fixtures that mimic upstream. - New behavior needs at least one unit test. New endpoints need an integration test.
- Run a focused subset with:
uv run pytest tests/unit/test_proxy_api_responses_contract.py -qReleases are automated via release-please:
- Commits land on
mainwith Conventional Commits titles. - release-please opens / updates a "chore(main): release X.Y.Z" PR containing the proposed version bump and generated changelog.
- When a maintainer merges that PR, a GitHub Release is published and the PyPI / Docker / Helm artifacts are built and uploaded.
Contributors never need to edit CHANGELOG.md, version strings, or tag
manually.
Stable releases are promoted from the beta channel, not cut directly:
- A
vX.Y.Z-beta.Nrelease ships first (the beta release PR). - The beta soaks on at least one production-scale deployment for at least 48 hours with no new regressions attributable to the release.
- Only then is the stable
vX.Y.Zrelease PR merged.
Rationale: migrations and proxy-path changes routinely behave differently at production data volumes than in CI. The beta soak is where migration duration, memory pressure, and upstream-protocol regressions surface without burning a stable version number.
Exceptions — a maintainer may promote directly to stable, noting the reason in the release PR, when the entire unsoaked delta consists of (any combination of):
- documentation, CI, or release-tooling changes, or
- a security or outage hotfix where waiting out the soak is the greater risk.
A train that also carries unrelated unsoaked changes must either soak as a beta or ship the hotfix separately.
Before merging a release PR whose train includes migrations, inspect the
Alembic revisions directly (git diff vPREV..HEAD -- app/db/alembic/versions)
for data backfills and estimate their duration against a production-sized
dataset — they run at startup and block serving until they finish. Changelog
titles do not reveal backfills.
The normative requirements live in
openspec/specs/release-management/
(delta: openspec/changes/require-beta-soak-before-stable/).
Please do not open public issues for security vulnerabilities. Report them privately via GitHub Security Advisories. See SECURITY.md for full details.
Happy hacking. If you get stuck, open a draft PR or start a Discussion — maintainers would rather see a half-finished idea than no idea at all.