Step 8: PR Preparation
You have a validated fix on a branch, a red-before/green-after test, and a clean
diff. Now you package it as a Pull Request a maintainer can review without asking
you a single clarifying question. Firecracker contribution is GitHub-native and
AWS-maintained: you fork, branch, push, open a PR against main, sign every
commit under the DCO, add a CHANGELOG line, request reviewers, and iterate until
two maintainers approve and one merges. There is no CLA, no JIRA, no
mailing-list patch. This step gets the PR to the line; Step 9
runs the review.
The bar for "merged-quality" is reached here, before any maintainer looks. Review latency is outside your control; craft is not.
Goal
A DCO-signed PR against firecracker-microvm/firecracker:main with: a branch of
one-logical-change commits, a CHANGELOG.md entry under the correct [Unreleased]
heading, a description that links the issue and explains the why, reviewers
requested, and green CI across the Buildkite matrix.
Branch, Rebase, and Final Diff
Work on a topic branch off an up-to-date main, never on main itself. Rebase
onto the latest upstream so your diff is against current code and CI runs clean:
git remote -v # 'origin' = your fork, 'upstream' = firecracker-microvm
git fetch upstream
git rebase upstream/main # replay your commits on current main
git diff upstream/main --stat # final sanity: only the predicted files
Resolve any rebase conflicts now, locally, where you have context — not in the PR
under review pressure. Re-run tools/devtool checkstyle and checkbuild --all
after the rebase; a rebase can surface a clippy lint a neighbouring change
introduced.
The CHANGELOG Entry
Every PR that changes behavior needs a CHANGELOG.md entry. Firecracker follows
Keep a Changelog: one ## [Unreleased] section at
the top with ### Added / Changed / Deprecated / Removed / Fixed subheadings, and
each entry is a bullet that leads with the PR link:
rg -n "## \[Unreleased\]|### Added|### Changed|### Fixed" CHANGELOG.md | head
## [Unreleased]
### Fixed
- [#NNNN](https://github.com/firecracker-microvm/firecracker/pull/NNNN): Fixed
the machine-config API accepting `vcpu_count` of 0, which deferred the failure
to `InstanceStart` instead of rejecting it at configuration time. The value is
now validated at the API boundary and returns a 400.
Pick the heading by user-facing effect, and be ready to defend it:
| Heading | Use when |
|---|---|
| Fixed | A bug fix that restores documented/expected behavior (most capstone fixes). |
| Changed | A behavior change that is not strictly a bug fix (a default, a message, a contract). |
| Added | A new capability, endpoint, or field. |
| Deprecated | Marking something for future removal (keep it working, warn). |
| Removed | Deleting a capability (a breaking change with its own weight). |
Note: You will not know the PR number until you open the PR, so add the entry with a placeholder, open the PR, then amend the placeholder to the real
#NNNNand force-push. That is normal and expected — do not skip the CHANGELOG to avoid the chicken-and-egg; a missing CHANGELOG is a fast "please add a CHANGELOG entry" round-trip. Match the exact wording/format conventions of recent entries you see with thergabove; the project is picky about it.
DCO Sign-Off: Get It Right Per Commit
Firecracker requires a Developer Certificate of Origin sign-off on every
commit — there is no CLA, the sign-off is the legal mechanism. The DCO bot
(github.com/apps/dco) checks that each commit has a Signed-off-by: line whose
name and email match the commit author. One unsigned commit fails the whole PR's
DCO check.
# -s appends the Signed-off-by line to every commit you make.
git commit -s -m "Reject vcpu_count of 0 in machine config validation
The machine-config handler accepted vcpu_count == 0, which the builder
assumes to be >= 1, deferring the failure to InstanceStart instead of a
clear 400 at configuration time. Validate the lower bound at the API
boundary and return MachineConfigError::InvalidVcpuCount.
Signed-off-by: Your Name <you@example.com>"
If you already committed without -s, fix it before pushing:
git commit --amend -s # the last commit
git rebase --signoff upstream/main # every commit on the branch (then force-push)
The email in Signed-off-by: must be the one tied to your GitHub account, and it
must match git config user.email. Get this right once, locally, before you ever
push — chasing a red DCO check across force-pushes is a waste of a review cycle.
See licensing-and-dco and
Lab 2.2.
Commit Hygiene
Firecracker wants each commit to be a single logical change that passes the build and tests on its own. This is what makes the PR reviewable commit-by-commit and bisectable later:
- One logical change per commit. If the fix legitimately needs two independent steps (add an error variant, then use it), two commits is fine; "fix + unrelated cleanup" in one commit is not.
- The production fix and its tests can be separate commits — it lets a reviewer see the fix, then the test that proves it, and lets you demonstrate the test is red without the fix commit applied.
- Title ≤ 72 chars, imperative mood ("Reject…", not "Rejected…"/"Fixes…"). The body wraps each line at ≤72 chars and explains the why — the symptom, the violated assumption, the fix. Reviewers read the body before the diff.
git log upstream/main..HEAD --format='%s' # each title imperative and ≤72?
Don't open a PR with a wip, fix CI, or address review commit history. Squash
the noise into logical commits before you push.
Push and Open the PR
git push -u origin your-branch-name
Open the PR against main with gh (or the web UI). The description is the
artifact a maintainer reads first — invest in it:
gh pr create --repo firecracker-microvm/firecracker --base main \
--title "Reject vcpu_count of 0 in machine config validation" \
--body-file capstone-work/pr-body.md
PR Description Template
Firecracker's PR template asks for the change, the reason, and a testing/checklist section. Fill all of it; an empty template section reads as "didn't read the contributing guide."
## Changes
Validate `vcpu_count >= 1` in the machine-config handler, returning
`MachineConfigError::InvalidVcpuCount` (HTTP 400) instead of letting `0`
flow to the builder and fail at `InstanceStart`.
## Reason
Fixes #NNNN. `firecracker.yaml` documents a minimum of 1, but the Rust path
never enforced it: `update_machine_config` accepted `0`, and the failure
surfaced later as an opaque builder error at `InstanceStart` rather than a
clear validation error at configuration time. Rejecting bad input at the API
boundary keeps internal code able to assume `vcpu_count >= 1`. This is an
original gap (bisect is good back to v1.A.0), not a regression.
## License Acceptance / Sign-off
By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache 2.0 license and signed off per the DCO.
## PR Checklist
- [x] Commits are signed per the DCO (`git commit -s`).
- [x] New integration test (`tests/integration_tests/functional/test_api.py`),
red on `main`, green here; unit test in `resources.rs`.
- [x] `CHANGELOG.md` updated under `### Fixed`.
- [x] `tools/devtool checkstyle` and `checkbuild --all` pass locally.
- [x] No new attack surface; seccomp filters unchanged; no snapshot-format change.
Tip: Use a GitHub closing keyword so the issue auto-closes on merge:
Fixes #NNNN/Closes #NNNN/Resolves #NNNNin the body. Reference the issue by number, link the introducing PR if it's a regression, and state the attack-surface/snapshot impact in one line each — those are the first questions a Firecracker maintainer asks.
Request Reviewers
CONTRIBUTING says to add two reviewers (a maintainer will do it for you if you don't, but doing it yourself signals you read the guide). Pick the right people, not random ones:
# Who maintains this area? Read MAINTAINERS.md and the file's recent history.
gh api repos/firecracker-microvm/firecracker/contents/MAINTAINERS.md --jq '.content' | base64 -d
git log -5 --format='%an <%ae>' -- src/vmm/src/resources.rs # who touches this code
Request the area maintainers from MAINTAINERS.md plus, if your bug is a
regression, the author of the introducing PR — they have the most context. You
need ≥2 maintainer approvals to merge, and a maintainer does the merge.
What CI Runs (and How to Stay Green)
Firecracker's CI is Buildkite (driven by .buildkite/pipeline_pr.py) plus a
few GitHub Actions checks. Know what runs so you can reproduce a failure locally
instead of pushing blind "fix CI" commits:
| Check | What it does | Run locally |
|---|---|---|
| DCO | Every commit has a matching Signed-off-by: line | `git log ... |
| style | ./tools/devtool checkstyle — fmt, clippy, cargo-sort, python/markdown style | tools/devtool checkstyle |
| build (per arch) | Builds the workspace for x86_64 and aarch64; clippy is -D warnings | tools/devtool checkbuild --all |
| integration tests | The pytest suite in tests/ on each arch | tools/devtool test |
| kani | Formal-verification harnesses (only if you touched verified code/test_kani.py) | tools/devtool test -- ../tests/integration_tests/test_kani.py |
| coverage | Tracks code coverage; it must not drop | the coverage pipeline; locally, ensure your unit test covers the fix |
| doc-only fast path | Doc-only changes skip the build group | n/a |
Warning: CI runs both architectures. A fix that compiles and passes on x86_64 can fail on aarch64 (a different
arch/path, a different default, a feature gated by arch). If your change is anywhere neararch/, build/test the other arch before you push — discovering it from a red CI runner costs a whole review cycle. Clippy is warnings-as-errors in CI even where it isn't locally by default;checkbuild --allreproduces that.
Watch your own CI after pushing:
gh pr checks --repo firecracker-microvm/firecracker <pr-number> --watch
If a check goes red, reproduce it locally and fix it locally. Pushing speculative "maybe this fixes CI" commits is a Step-9 failure signal; the gates all have a local equivalent above.
Deliverable for Step 8
-
Branch rebased onto
upstream/main;git diff upstream/main --statshows only the predicted files. -
Every commit DCO-signed (
-s), one logical change each, imperative ≤72-char titles, why-focused bodies wrapped at 72. -
CHANGELOG.mdentry under the correct[Unreleased]heading, leading with the PR link, matching the project's format. -
PR opened against
mainwith a description that links the issue (Fixes #NNNN), explains the why, and states attack-surface/snapshot/perf impact. -
PR number back-filled into the CHANGELOG entry and (if used) any in-code
#NNNNcomment, then force-pushed. - Two reviewers requested (area maintainers + introducing-PR author if a regression).
- CI green across DCO, style, build (both arches), and tests.
Rubric Hooks
This is the PR craft dimension (12 pts): DCO on every commit, a correct CHANGELOG entry, a description that links the issue and explains the why, clean one-logical-change commits, and green CI. It also opens Communication (10 pts): requesting the right reviewers and a description that pre-empts questions. A DCO-clean, well-described PR with a CHANGELOG and green CI scores high; a missing sign-off, an empty template, or a "fix CI" commit history scores low. See the evaluation rubric.
Validation / Self-check
Before advancing to Step 9:
- Every commit has a
Signed-off-by:line matching its author email; the DCO check is green. - The branch is rebased onto current
upstream/mainand CI is green across DCO, style, build (both arches), and tests. CHANGELOG.mdhas an entry under the right heading, leading with the PR link, in the project's format.- The PR description links the issue with a closing keyword, explains the why, and names the attack-surface/snapshot/perf impact.
- Commits are one-logical-change with imperative ≤72-char titles; there is no
wip/fix CInoise in the history. - You requested the right reviewers and understand you need ≥2 maintainer approvals to merge.
Then go to Step 9: Engaging on GitHub & Docs.