Stage 1 — Documentation and Test-Only Fixes
What class of issue this is
Stage 1 is the on-ramp. The changes themselves are surgical — a typo in docs/, a stale flag in a
help string, an outdated code comment, a missing or weak unit test — but the real deliverable is
the GitHub contribution workflow that every later stage assumes and never re-explains. You are
exercising the mechanics so the next eleven stages can be about code.
Concretely, a Stage 1 PR is one of:
- A documentation fix in
docs/(getting-started, jailer, seccomp, snapshotting, prod-host-setup), in a top-level.md(README.md,CONTRIBUTING.md,FAQ.md,SPECIFICATION.md), or in a doc-comment (///) on a Rust item. - A correction to an out-of-date code comment or a misleading log/help/error string.
- A new or strengthened unit test (
#[test]in amod tests) for code that is under-tested, or a small pytest integration test that pins existing behaviour.
Nothing in this stage should surprise a reviewer. That is the point.
Note: Firecracker is single-vendor: a dedicated AWS team owns it. There is no JIRA, no patch file, no CLA. The unit of work is a branch on your fork and a PR against
firecracker-microvm/firecracker'smain. Every commit needs a DCO sign-off (git commit -s). Re-rolls are follow-up commits (or a force-push, with etiquette).
Why it's at this difficulty
These changes touch text and tests, not behaviour. A docs fix cannot crash a guest; a strengthened
assertion cannot regress production. So the bar is purely can you operate the workflow cleanly —
sign-off present, CHANGELOG updated when needed, tools/devtool checkstyle green, one focused
change. Maps to Level 1 (build/test/boot) and Level 2
(the PR workflow).
What you must already understand
- You can build Firecracker:
tools/devtool buildproducesbuild/cargo_target/<arch>-unknown-linux-musl/debug/firecracker(verify the arch/profile on your machine). - You can run the suites:
tools/devtool test(the pytest integration harness) andcargo testfor unit tests. The integration suite is Python-driven — it is not rawcargo test. - You have
gitconfigured with the name/email you use on GitHub, sogit commit -sproduces aSigned-off-by:line the DCO bot will accept.
If any of that is shaky, do Level 1 first.
The GitHub workflow, end to end
Step 0 — Fork, clone, branch
gh repo fork firecracker-microvm/firecracker --clone # or fork in the UI, then clone your fork
cd firecracker
git remote add upstream https://github.com/firecracker-microvm/firecracker.git
git fetch upstream
git checkout -b docs/fix-jailer-cgroup-typo upstream/main
Keep main clean and tracking upstream; branch for every change.
Step 1 — Find an issue
# Curated small issues, unassigned, freshest first:
gh issue list --repo firecracker-microvm/firecracker \
--state open --label "good first issue" --search "no:assignee sort:updated-desc" --limit 30
# Documentation-typed issues (label string may differ — verify on the tracker):
gh issue list --repo firecracker-microvm/firecracker \
--state open --label "Type: Documentation" --search "no:assignee" --limit 30
Open three candidates, read each thread end to end, and pick one with no assignee and no open PR linked. Comment to claim it:
I'd like to take this. Planning to <one sentence>. Will open a PR shortly.
Step 1b — Or find your own (the common case here)
In a single-vendor project, good first issue is often thin. Most Stage 1 work you will find
yourself by grepping the tree for stale text and under-tested code:
# Stale flag / option text in docs and help strings:
rg -n "enable_diff_snapshots|mem_file_path" docs/ src/ # both were renamed/deprecated
rg -n "TODO|FIXME|XXX" docs/ CONTRIBUTING.md README.md
# Doc-comments that contradict the code, or missing module docs:
rg -n "^///|^//!" src/vmm/src/devices/virtio/block/ | head
# Under-tested code: public fns with no nearby #[test], or weak asserts:
rg -n "fn .* -> Result" src/vmm/src/vmm_config/ | head
rg -n "assert!\(.*\.is_ok\(\)\)" src/ # asserts success but not the value
A genuine stale-doc or weak-test smell found this way is fair game. For a non-trivial behaviour question, file an issue first; for an obvious typo, a PR with a clear description is fine.
Representative tasks
| Task | Where it lives | Find it with | Test/gate |
|---|---|---|---|
Fix a typo / stale flag in docs/ | docs/*.md, docs/snapshotting/ | rg -n "enable_diff_snapshots" docs/ | tools/devtool checkstyle (mdformat) |
| Correct a misleading doc-comment | src/vmm/src/.../*.rs | rg -n "^///" <file> | cargo doc, cargo test |
| Fix an out-of-date help/usage string | src/firecracker/src/, src/jailer/src/ | `rg -n "Arguments::new | .help(" src/` |
| Strengthen a weak unit-test assertion | src/.../mod tests | `rg -n "is_ok()) | unwrap()" src/.../tests` |
| Add a missing unit test for a pure fn | next to the fn under mod tests | rg -n "pub fn" <module> | cargo test -p <crate> |
| Add a small pytest pinning existing behaviour | tests/integration_tests/ | rg -n "def test_" tests/ | tools/devtool test -- -k <name> |
How to approach one — worked example: a documentation fix
Illustrative of the pattern. The
rgfinds the real site on your branch; paths drift after the crate refactor, so run the command rather than trusting a path.
Symptom: docs/snapshotting/ still references the old request field enable_diff_snapshots,
which was renamed to track_dirty_pages (and a standalone mem_file_path on load was deprecated).
rg -n "enable_diff_snapshots|mem_file_path" docs/
git log --oneline -n 5 -- docs/snapshotting/
The diff describes behaviour, not the identifier, and matches the current API:
--- a/docs/snapshotting/snapshot-support.md
+++ b/docs/snapshotting/snapshot-support.md
@@
-To take a diff snapshot, set `enable_diff_snapshots` to `true` in the machine config.
+To take a diff snapshot, enable dirty-page tracking by setting `track_dirty_pages` to
+`true` in `PUT /machine-config`. Diff snapshots only capture pages dirtied since the base.
Confirm against the API surface before you write the sentence — never from memory:
rg -n "track_dirty_pages" src/firecracker/swagger/firecracker.yaml src/vmm/src/vmm_config/
Run the docs gate, commit with sign-off, push, open the PR:
tools/devtool checkstyle # runs mdformat among other checks
git add docs/snapshotting/snapshot-support.md
git commit -s -m "docs: rename enable_diff_snapshots to track_dirty_pages in snapshot guide"
git push origin docs/fix-snapshot-flag
gh pr create --repo firecracker-microvm/firecracker --fill
Then watch CI: the DCO check is green only if every commit is signed off; style/build run the
same checkstyle/checkbuild you ran locally. Read failures top to bottom.
How to approach one — worked example: strengthening a unit test
Illustrative. Run the grep to find a real candidate.
Symptom: a test exercises a config parser but asserts only that the result is Ok(...) — a
regression that produced a wrong value would still pass. A classic weak-assertion smell.
rg -n "assert!\(.*\.is_ok\(\)\)" src/vmm/src/vmm_config/ | head
--- a/src/vmm/src/vmm_config/machine_config.rs
+++ b/src/vmm/src/vmm_config/machine_config.rs
@@ mod tests {
#[test]
fn test_default_machine_config() {
let cfg = MachineConfig::default();
- assert!(MachineConfigUpdate::from(cfg.clone()).update(&cfg).is_ok());
+ let updated = MachineConfigUpdate::from(cfg.clone()).update(&cfg).unwrap();
+ assert_eq!(updated.vcpu_count, 1);
+ assert_eq!(updated.mem_size_mib, 128);
+ assert!(!updated.smt);
}
Warning: Do not change the production code in a Stage 1 test PR. If strengthening the assertion reveals a real bug (the value is genuinely wrong), that is a Stage 3 or Stage 4 fix — open a separate issue and PR. Mixing a test improvement with a behaviour change is the fastest way to get a Stage 1 PR bounced.
Run just this test, then commit:
cargo test -p vmm machine_config::tests::test_default_machine_config
What a good PR looks like
- One concern. A docs fix or a test, never both, never "while I was here…".
- DCO on every commit.
git commit -s; fix a missing sign-off withgit commit --amend -s(one commit) orgit rebase --signoff upstream/main(the whole branch), then force-push. - CHANGELOG only when user-visible. A pure docs/test PR usually needs no
CHANGELOG.mdentry; a changed help string or behaviour does. Follow the PR template's checkbox and the repo convention. - Title ≤ 72 chars, imperative, prefixed by area (
docs:,test:). - Green local gates before you push:
tools/devtool fmtthentools/devtool checkstyle. Clippy is warnings-as-errors, so even a docs PR that touches Rust must passcargo clippy --all --all-targets --all-features -- -D warnings. - A description a reviewer can verify in 30 seconds: what was wrong, what it is now, and the command that proves it.
Graduation criteria — ready for Stage 2 when
- You have one merged docs/comment PR and one merged test-only PR (a strengthened assertion or a genuinely missing test).
- You responded to at least one round of reviewer nits with follow-up commits, without anyone having
to explain
git commit -sor the CHANGELOG to you. - A green CI run no longer makes you anxious, and you can read a red one and tell whether the failure is yours or a pre-existing flake (Stage 9 territory).
- You can recite the loop from memory: fork → branch → change → CHANGELOG (if user-visible) →
git commit -s→tools/devtool fmt→tools/devtool checkstyle→checkbuild --all→ push → PR → read CI → respond with follow-up commits.
If, while fixing a Stage 1 issue, you find a bigger problem, do not bundle it. File a follow-up issue and keep the PR narrow. That discipline is what the entire roadmap depends on.