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 a mod 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's main. 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 build produces build/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) and cargo test for unit tests. The integration suite is Python-driven — it is not raw cargo test.
  • You have git configured with the name/email you use on GitHub, so git commit -s produces a Signed-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

TaskWhere it livesFind it withTest/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-commentsrc/vmm/src/.../*.rsrg -n "^///" <file>cargo doc, cargo test
Fix an out-of-date help/usage stringsrc/firecracker/src/, src/jailer/src/`rg -n "Arguments::new.help(" src/`
Strengthen a weak unit-test assertionsrc/.../mod tests`rg -n "is_ok())unwrap()" src/.../tests`
Add a missing unit test for a pure fnnext to the fn under mod testsrg -n "pub fn" <module>cargo test -p <crate>
Add a small pytest pinning existing behaviourtests/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 rg finds 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 with git commit --amend -s (one commit) or git rebase --signoff upstream/main (the whole branch), then force-push.
  • CHANGELOG only when user-visible. A pure docs/test PR usually needs no CHANGELOG.md entry; 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 fmt then tools/devtool checkstyle. Clippy is warnings-as-errors, so even a docs PR that touches Rust must pass cargo 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 -s or 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.

Next: Stage 2 — Build, Tooling, and Logging.