These guidelines provide standard default conventions for repository operations. Explicit user instructions always take precedence and override these guidelines.
OpenAirInterface 5G: 4G/5G RAN stack (UE, eNB, gNB) implementing 3GPP standards.
Use cmake directly, not the build_oai wrapper (the exception: ./build_oai -I once, to
install system dependencies on a fresh machine).
mkdir -p build && cd build
cmake .. -GNinja
ninja # or target it: ninja nr-softmodem nr-uesoftmodemopenair1: PHY layeropenair2: MAC / RLC / PDCP / RRC / SDAP layersopenair3: NAS / NGAP / GTP layerscommon: Shared utilities, config module, platform typesnfapi: nFAPI / FAPI (PNF/VNF split) messages and integrationradio: Radio drivers (rfsimulator, USRP, etc.)fronthaul: Native OAI 7.2x fronthaul splitexecutables: gNB / UE / CU / DU top-level main entrypointscmake_targets:build_oaiwrapper, dependency installation, physim test liststests: Standalone test executables (CU-UP, NAS simulator, DL benchmark)ci-scripts: Official CI test harness and execution scripts (see Verification below)docker: Dockerfiles for build and runtime imagescharts,openshift: Helm charts and OpenShift build configs used by CItargets: Example configuration files (targets/PROJECTS/GENERIC-NR-5GC/CONF)doc: Tutorials, feature docs, code style (doc/code-style-contrib.md)tools: Formatting, IWYU, cppcheck, dev-env and helper scripts
See doc/code-style-contrib.md. In short: 2-space indent, no tabs, 132-column
limit, respect .clang-format, strong types (c16_t not uint32_t for IQ
samples), AssertFatal()/DevAssert() for invariants — not error handling.
Don't leave temporary debug logging (LOG_E/print statements added only to
trace a bug) in code you consider finished — remove it before reporting the
task done.
- Be concise; write for a domain expert, not a beginner.
- Comment the why (a non-obvious constraint, workaround, or invariant), not the what.
- When implementing part of a 3GPP spec, cite the concrete section (e.g.
TS 38.331 §6.2.2), not just the spec number. - Don't narrate the edit ("was X, now Y because Z", "removed old A") — put that in the commit message; describe only the resulting code.
- Don't leave "in-progress"/WIP/TODO markers in code you consider finished.
Run relevant verification stages based on the code modified. Agents must use good judgment to
select which tests to run based on which subsystem was changed. For instance, modifying
openair1 (PHY layer) usually means running physical layer simulation tests
(ctest -R '^physim\.' / phytest).
- Functional unit tests —
ctest -E '^physim\.|^benchmark_'inbuild/. - Isolated benchmarks —
ctest -R '^benchmark_'. Skip unless the change touches perf-sensitive code (PHY inner loops, RLC, scheduler). - Physim (phytest) —
ctest -R '^physim\.'. Essential when making changes toopenair1. Requires configuring with-DENABLE_PHYSIM_TESTS=ON(the executables build unconditionally regardless of that flag; 0 matched tests means the flag is missing — reconfigure and rerun, don't treat 0 as a pass). Full run is slow; for a fast pass across every physim executable, usectest -L quick_physim(well under 3 minutes) — good enough to catch a broken change quickly, but still not a substitute for the full set before release. - Affected CI test case — if the change affects RRC/NAS/PDU-session or rfsim behavior, run
the testcase through
ci-scripts.run_locally.shtakes a path to an XML testcase relative toci-scripts/. Build the required images first, then run:Rebuild the images after any code change before rerunning — Docker layer caching can silently serve a stale binary if only the final stage rebuilds.docker build . -f docker/Dockerfile.base.ubuntu -t ran-base docker build . -f docker/Dockerfile.build.ubuntu -t ran-build docker build . -f docker/Dockerfile.gNB.ubuntu -t oai-gnb docker build . -f docker/Dockerfile.nrUE.ubuntu -t oai-nr-ue docker build . -f docker/Dockerfile.nr-cuup.ubuntu -t oai-nr-cuup cd ci-scripts && ./run_locally.sh xml_files/container_5g_rfsim.xml
Lessons:
- An equivalence/regression test must call the real production functions, not re-derive the expected values independently — a self-consistent-but-wrong test can pass for a long time while the code under test is broken.
- Green unit tests don't mean the feature works end-to-end — verify live
(docker
ci-scriptsrun or manual rfsim below) before calling it done. - If a new feature fails, run the known-good baseline through the same path first to rule out an environment issue before debugging the feature.
- Hung process with no log output:
gdb -p <pid> -batch -ex 'thread apply all bt'beats another add-logging-and-rebuild cycle.
For interactive checks beyond the automated stages, run gNB and UE as plain host processes against a dockerized 5G core:
cd build
sudo ./nr-softmodem -O ../ci-scripts/conf_files/gnb.sa.band78.106prb.rfsim.yaml -E --rfsim \
--gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NG_AMF 192.168.71.190 --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.190
sudo ./nr-uesoftmodem -O ../ci-scripts/conf_files/nrue.uicc.yaml -E --rfsim -r 106 --numerology 1 -C 3319680000Present what you observe (RRC state, asserts, segfaults, "Bye." on shutdown) without declaring pass/fail yourself — that judgment call is the user's.
Follow the Commit Guidelines. In particular:
- Never write a
Signed-off-by:orCo-authored-by:trailer yourself, nor infer one from git config or history — only the human submitter can certify the Developer Certificate of Origin. - Add an
Assisted-by: AGENT_NAME:MODEL_VERSIONtrailer to every commit an agent authored or materially edited, e.g.Assisted-by: Claude:claude-opus-5. - Commit only when the user asks you to, and then with
git commit -s -S: the request is the user's authorization for-s(theirSigned-off-by:from their git identity) and-S(their key) — see Signing Commits. Otherwise leave the changes for the user to commit. Never change the signing configuration or bypass it with--no-gpg-sign; report a signing failure instead. - One logical change per commit; subject preferably at most 72 characters.
- Commit messages are concise, written for a domain expert, and explain the resulting change and its rationale; 3GPP behavior cites the concrete spec section (see Comments).
- Trailers go at the end of the message after a blank line, one
Key: valueper line, using only the keys from these guidelines (Signed-off-by:,Assisted-by:,Fixes: <commit>,Closes: #<issue>). The value is the bare reference with no explanation; reasoning belongs in the message body. AddFixes:/Closes:only when the reference is verified. For more details, refer to contribution guidelines. - Feature branches are based on the latest
developand contain no merge commits. Update one by rebasing (when asked, see below), never by mergingdevelopin. Fetching is fine. - Before handing a branch over for a pull request, run
ci-scripts/pre-ci-check.sh(defaults: current branch againstorigin/develop) and fix what it reports: merge commits and commits missing the human submitter'sSigned-off-by:.
Agents must never do any of the following without being explicitly asked to:
- Push or update a remote or upstream branch, create a pull request, edit its title or description, or post a comment on one.
- Rewrite history (rebase, amend, squash), pushed or not, or force-push.
- Delete branches or tags, locally or on a remote, or create or push tags.
- Add or remove pull request labels.
- Push images to a container registry.
- Add or update third-party dependencies, or change license headers.
- Prefer adding new tests to
ctestwhen you write them. Agents should always try to generate unit tests for new features or bug fixes. - When asked to prepare a PR, give the user a draft description: rationale, before/after narrative, alternatives considered, verification results, and diagrams where useful. The long-form narrative belongs there; commit messages keep only the concise rationale.
- Check
CONTRIBUTING.mdfor licensing/contribution requirements.