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.
- Codex Review (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. @codex reviewmust be clean — or its findings addressed — on the merge-target head. Every PR triggers@codex reviewat least once against the head that's about to be merged. Localcodex review --base origin/mainruns are encouraged but don't substitute for the cloud review (the cloud@codex reviewreliably catches things the local run misses). The🤖 codex: oklabel is maintained by the trustedCodex review labelsworkflow from current-head CI and current-head Codex review evidence. Treat the label as an audit aid, not as a substitute for branch protection or merge queue checks.- 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.
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,
@codex reviewclean or 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
@codex reviewfor every PR. Codex 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.
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.