Level 8: Real Issue Contribution
Every level before this one was curated. You built Firecracker from a known-good baseline, you traced
the vCPU run loop someone chose for you, you wired up an API action against a spec, and you added a
test where the expected outcome was already understood. Level 8 ends the scaffolding. Here you do
what a Firecracker maintainer does on a normal Tuesday: take a real, open GitHub issue from a stranger's
bug report, reproduce it deterministically on your own checkout, root-cause it by walking the actual
execution path, fix it with the smallest correct diff, write the test that fails before and passes
after, run the full tools/devtool gate, and open a pull request that two AWS maintainers will read
critically before either of them approves it.
There is no answer key. The issue may be mis-triaged. The "obvious" fix may break a snapshot compatibility guarantee or widen the seccomp surface. The reproduction may be racy because the bug is in the EventManager epoll loop, not in the code the reporter blamed. That is the job. This level is where the skills from Levels 1–7 stop being exercises and start being leverage — the only reason you can reproduce a virtio-block hang is that you traced one in Level 7, and the only reason you can read an API validation failure is that you walked the action channel in Level 3.
This curriculum will not hold your hand here least of all. It points you at the issue tracker, gives you the right questions, and makes you run every command. The output of this level is not a passing lab — it is a branch the maintainers would merge.
Learning Objectives
By the end of Level 8 you must be able to:
- Find a tractable open issue in
firecracker-microvm/firecrackerand judge honestly whether it is in scope for you right now — small surface, readable subsystem, no live PR. - Build a deterministic reproducer that fails on your build of
mainand will pass after the fix — a config file, acurl --unix-socketsequence, or (ideally) a failing pytest integration test or acargo testunit test. - Separate the symptom (what the reporter saw) from the trigger conditions (the minimal set of inputs that actually provoke it), varying one factor at a time.
- Root-cause a defect by walking the execution path with
rg— from the API endpoint or config field, throughVmmAction, into the builder, the device, or the vCPU, down to the KVM ioctl if needed. - Implement a minimal, correct fix that respects the threat model, the snapshot/API compatibility guarantees, and the minimal-device-model philosophy.
- Write the required test (integration test for new behavior; unit test for pure logic) that is
red before your change and green after, and pass
tools/devtool checkstyle+checkbuild --all+test. - Author the CHANGELOG entry, commit with DCO sign-off (
git commit -s), and open a PR whose description a reviewer can act on without asking you a single clarifying question. - Map a given bug class onto the codebase areas you learned in Levels 3–7, so you know where to look before you start grepping.
The Issue Lifecycle (the loop everything below runs on)
Everything in this level — and the Capstone — is one loop. Internalize it; you will run it for the rest of your contributor life.
flowchart LR
F[Find issue<br/>Type: Bug / good first issue] --> R[Reproduce<br/>fails on main, every run]
R --> P[Execution path<br/>rg from symptom to cause]
P --> C[Root cause<br/>the real defect, not the symptom]
C --> X[Fix<br/>minimal, correct diff]
X --> T[Test<br/>red before, green after]
T --> G[Gate<br/>devtool checkstyle + checkbuild + test]
G --> L[CHANGELOG + sign-off]
L --> PR[PR<br/>links the issue, clear description]
PR --> CI[CI + 2 maintainer reviews]
CI -->|changes requested| X
CI -->|2 approvals| M[A maintainer merges]
The non-obvious truth, and the thing that separates serious contributors from drive-by ones: the reproducer, not the fix, is the center of gravity. A bug you can reproduce on demand is a bug you can fix with confidence and prove you fixed. A bug you cannot reproduce is a guess dressed as a patch, and maintainers reject those. Lab 8.1 is entirely about building the reproducer; Lab 8.2 turns it into a merged-quality PR.
Note: "Reproduce on demand" has a precise meaning in a VMM. For a pure-logic bug (a parser, a validator, a rate-limiter calculation) it means a deterministic
cargo test. For a behavioral bug (a device hang, a boot failure, a wrong API status code) it means a pytest integration test on a real microVM. For a timing/race bug it may mean a test that fails often, with the conditions pinned. All three count — fabricating a clean unit test for a race you cannot actually trigger does not.
How This Level Differs From Levels 1–7
| Earlier levels | Level 8 |
|---|---|
| Curated labs with a known answer | Real open issues, possibly mis-triaged |
| Execution path chosen for you | You locate the code from a symptom |
| Repro provided | You build the repro from a prose bug report |
| "Make it compile / make the test pass" | "Make two maintainers say yes" |
| No external stakes | A real PR, real CI, real reviewers, real compatibility constraints |
| One subsystem at a time | The bug spans whatever subsystems it spans |
You bring the whole stack from prior levels:
| You learned in… | …and use it in Level 8 to… |
|---|---|
| L3 — API & threading | trace PUT /... → ParsedRequest → VmmAction → the VMM thread |
| L4 — vCPU & KVM | reason about a wrong VM-exit handling or a CPUID/MSR defect |
| L5 — testing | write the pytest integration test or cargo test that gates the fix |
| L6 — boot & memory | diagnose a boot-config, e820, or guest-memory bug |
| L7 — virtio | diagnose a virtqueue, MMIO-transport, or block/net/vsock bug |
Mapping Bug Classes to the Codebase
Before you grep blindly, know where a class of bug lives. This table is your starting map; always
rg to confirm the current path on your branch — the big crate merge moved most subsystems into
src/vmm/src/.
| Bug symptom (from the report) | Likely subsystem | Where to start (rg/path) |
|---|---|---|
| API returns wrong status / accepts bad input | API server + request parsing + VmmAction validation | src/firecracker/src/api_server/, src/vmm/src/rpc_interface.rs |
| Config field silently ignored or mis-applied | vmm_config/ parsing + VmResources | rg -n "deserialize|struct .*Config" src/vmm/src/vmm_config/ |
| Wrong/opaque error on bad config | the error enums for that subsystem | rg -n "enum .*Error" src/vmm/src/vmm_config/ |
| microVM fails to boot / panics on start | the builder + boot config + kernel load | rg -n "build_microvm_for_boot|configure_system" src/vmm/src/ |
| Device hangs, stalls, or corrupts data | the virtio device + its virtqueue handling | rg -n "fn process_queue|fn handle_queue" src/vmm/src/devices/virtio/ |
| Wrong CPUID/MSR in guest | CPU templates / cpu_config | rg -n "fn normalize|KVM_SET_CPUID2|set_cpuid" src/vmm/src/ |
| Snapshot restore fails / incompatible | persist.rs + the device's Persist impl | rg -n "impl Persist|fn save|fn restore" src/vmm/src/ |
| Wrong rate-limit behavior | the token bucket | rg -n "struct TokenBucket|fn reduce" src/vmm/src/rate_limiter/ |
| Jailer/seccomp denies a legit syscall | seccomp filter JSON or jailer setup | resources/seccomp/, src/jailer/src/ |
Tip: The fastest way to find the code that produced a message a user pasted into an issue is to grep for a fixed substring of that message. Run
rg -n "the unique phrase from the error" src/before anything else. If the message is built from a format string, grep the static prefix.
Required Reading
Read these before Lab 8.1. The point is to learn the project's own standards from its own documents and from recent merged PRs — not from this book paraphrasing them.
| Source | What to extract |
|---|---|
CONTRIBUTING.md (repo root) | The exact PR rules: DCO sign-off, one logical change per commit, integration tests for new behavior, the devtool checks, ≥2 approvals |
CHANGELOG.md (repo root) | The entry format and the ### Added/Changed/Fixed/Deprecated/Removed sections — you will add a line |
tests/README.md (and tests/) | How the pytest integration harness is run and structured |
docs/ for the subsystem you touch | The intended behavior, so you fix toward the spec, not your guess |
| Recent merged PRs (below) | Live exemplars of diff size, test style, commit message, and CHANGELOG line that actually got merged |
Confirm the docs exist on your branch, then study real merged PRs as your gold standard:
# Confirm the contribution docs are present on your checkout.
ls CONTRIBUTING.md CHANGELOG.md SPECIFICATION.md docs/ tests/
# The single most useful study material: PRs the maintainers actually merged.
# (Requires the GitHub CLI, authenticated: gh auth login)
gh pr list --repo firecracker-microvm/firecracker --state merged --limit 30
# Filter to bug-fix exemplars, then read a few end to end (diff + discussion).
gh pr list --repo firecracker-microvm/firecracker --state merged --label "Type: Bug" --limit 20
gh pr view <NUMBER> --repo firecracker-microvm/firecracker
gh pr diff <NUMBER> --repo firecracker-microvm/firecracker
When you read a merged bug-fix PR, answer for yourself: How many lines did it touch? Where was the test, and does it visibly map to the bug? What did the CHANGELOG line say? How was the issue linked in the description? Those four answers are the merge bar.
Source Code Areas to Inspect
You will not know which of these you need until you have the issue. Survey them now so the map is in your head.
| Area | Path (verify with find/rg) | Why it matters at Level 8 |
|---|---|---|
| API server + request parsing | src/firecracker/src/api_server/ | Where bad input should be rejected; many bug reports start here |
| Action enum + dispatch | src/vmm/src/rpc_interface.rs | VmmAction, VmmActionError, PrebootApiController/RuntimeApiController |
| Config parsing & validation | src/vmm/src/vmm_config/ | The most common home of validation and error-message bugs |
| The builder | src/vmm/src/builder.rs | Boot-time failures and ordering bugs |
| Devices | src/vmm/src/devices/virtio/{block,net,vsock,balloon,rng}/ | Hangs, stalls, data bugs |
| vCPU / KVM state | src/vmm/src/vstate/vcpu/, vstate/vm.rs | VM-exit handling and KVM-error reporting |
| Snapshot persistence | src/vmm/src/persist.rs, src/vmm/src/snapshot/ | Restore failures, version-compat |
| Tests | tests/integration_tests/ (pytest), *_test.rs / #[cfg(test)] (Rust) | Where your fix's test goes |
# Orient yourself on the current workspace before you dive.
find src -maxdepth 2 -name Cargo.toml # crate boundaries
rg -n "enum VmmAction" src/vmm/src/rpc_interface.rs
rg -n "enum VmmActionError" src/vmm/src/rpc_interface.rs
Key Types Quick Reference
Names by role, each with the command that finds it on your branch. Never trust a line number.
| Type / item | Role | Find it |
|---|---|---|
VmmAction | Control-plane command from API → VMM thread | rg -n "enum VmmAction" src/vmm/src/rpc_interface.rs |
VmmActionError | What a failed action returns to the API caller | rg -n "enum VmmActionError" src/vmm/src/rpc_interface.rs |
VmResources | Aggregated pre-boot config | rg -n "struct VmResources" src/vmm/src/resources.rs |
ParsedRequest | A parsed HTTP request, pre-dispatch | rg -n "struct ParsedRequest|enum RequestAction" src/firecracker/src/ |
build_microvm_for_boot | Cold-boot builder entry | rg -n "fn build_microvm_for_boot" src/vmm/src/builder.rs |
The subsystem …Error enum | Per-subsystem error type (often thiserror) | rg -n "enum .*Error" src/vmm/src/vmm_config/ |
Persist trait | Save/restore for snapshot-able state | rg -n "trait Persist" src/vmm/src/ |
The device process_* fns | Virtqueue processing (hang/stall bugs) | rg -n "fn process_" src/vmm/src/devices/virtio/ |
GitHub Issue Categories for Level 8
These are the classes you can credibly take on now. Find them with real label filters (verify label
spellings on the tracker — Firecracker uses Type: … and Status: … and Priority: … with colons):
# Good first issues — maintainer-vetted as small and self-contained.
gh issue list --repo firecracker-microvm/firecracker --state open --label "good first issue"
# Bugs — sorted by most recently updated (active, likely still valid).
gh issue list --repo firecracker-microvm/firecracker --state open --label "Type: Bug" --limit 40
# Enhancements that are genuinely small (verify each — most need design buy-in first).
gh issue list --repo firecracker-microvm/firecracker --state open --label "Type: Enhancement"
| Category | Typical size | Where it lives | Your lab |
|---|---|---|---|
| Config/API validation accepts bad input | small | vmm_config/, rpc_interface.rs, api_server | 8.1 + 8.2 |
| Opaque/poor error message on a real failure | small | error enums across subsystems | 8.3 |
| Device edge case (off-by-one, bad descriptor handling) | medium | devices/virtio/ | 8.2 |
| Boot-config or memory-layout bug | medium | builder.rs, arch/, vmm_config/ | 8.2 |
| Missing or wrong test for existing behavior | small–medium | tests/, *_test.rs | builds on Level 5 |
Warning: Before you sink a day into an issue, read the entire thread and check for a linked PR. The community norm is announce intent, then work: comment that you are investigating so two people do not fix the same bug.
gh issue view <N> --repo firecracker-microvm/firecracker --comments.
Deliverables
You must produce all of the following before advancing to Level 9:
- A chosen real (or real-feeling) open issue with a written triage note: scope, subsystem, assignee/PR check, and a plan.
-
A deterministic reproducer that fails on your build of
main(Lab 8.1). -
A walked execution path from symptom to root cause, written down (the
rgtrail). - A minimal-diff fix plus a test that is red before, green after (Lab 8.2).
-
A green run of
tools/devtool checkstyle,checkbuild --all, andtest. -
A
CHANGELOG.mdentry and DCO-signed commits (git commit -s). - A PR (or a complete PR-ready branch with a filled description, if not submitting upstream).
- One diagnostics improvement with a test asserting the new message (Lab 8.3).
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Fixing before reproducing | You patch the wrong thing and cannot prove it works | Build the reproducer first; it must fail on main |
| Test that passes before the fix | Proves nothing; a reviewer will catch it | Revert the fix and confirm the test goes red |
| Patching the symptom, not the cause | Bug returns via another path | Walk the execution path to the real defect |
| Drive-by reformatting in the diff | Reviewer cannot see the real change; fmt churn | Keep the diff minimal; never bundle a reformat |
| Skipping the integration test for new behavior | CONTRIBUTING requires it; PR stalls | Add a pytest integration test, not just a unit test |
| Forgetting the CHANGELOG entry | Reviewers ask for it; round-trip wasted | Add a line under the right ### section |
Forgetting git commit -s | The DCO bot blocks the PR | Sign off every commit; amend with --amend -s if needed |
| Widening seccomp or the device surface to "fix" a bug | Violates the threat model; near-certain rejection | Find the fix that does not add attack surface |
| Bundling two fixes in one PR | Hard to review; stalls | One issue, one PR; rebase, don't bundle |
| Picking an enhancement as a first PR | Needs design discussion; spins for weeks | Start with Type: Bug / good first issue |
How to Verify Success
# 1. Your reproducer is red on a clean main, green on your branch.
git stash && tools/devtool test -- integration_tests/.../test_your_repro.py # expect FAIL
git stash pop && tools/devtool test -- integration_tests/.../test_your_repro.py # expect PASS
# (or, for a unit-test repro:)
git stash && cargo test -p vmm your_repro_test # FAIL on main
git stash pop && cargo test -p vmm your_repro_test # PASS on your branch
# 2. The full local gate is green.
tools/devtool checkstyle
tools/devtool checkbuild --all
tools/devtool test
# 3. Every commit is signed off, and the CHANGELOG mentions your change.
git log --format='%H %G? %s%n%b' | rg -i "signed-off-by" -A0 -B1
git diff main -- CHANGELOG.md
If the reproducer fails on main, passes on your branch, the gate is green, the diff is minimal, the
CHANGELOG names the change, and the commits are signed — you have a PR a maintainer can review.
PR Profile: Level 8 Graduate
A Level 8 graduate can credibly open these PRs against firecracker-microvm/firecracker:
| PR type | Example | Why you can do it now |
|---|---|---|
| Config/API validation fix | Reject an out-of-range mem_size_mib or a malformed drive config with a clean 400 | You can trace the API → action → validation path and test it |
| Error-message improvement | Turn an opaque KVM/ioctl or config failure into an operator-actionable message | You understand the error enums and what an operator needs (Lab 8.3) |
| Device edge-case fix | Handle a malformed virtio descriptor chain without panicking | You traced virtio in Level 7 and can write the pytest test |
| Boot/memory-config bug fix | Correct a boot-args or layout edge case | You traced boot in Level 6 |
| Missing-test contribution | Add the integration test that an under-tested behavior was missing | Builds directly on Level 5 |
| Diagnostics/logging fix | Add context (the offending value, the device id) to a failure log | Low-risk, high-value, reviewer-friendly |
What this graduate does not yet open: new devices, new API endpoints, snapshot-format changes, or anything touching the seccomp/jailer security boundary as a feature. Those require design discussion and the depth of Level 9 and the Capstone.
Next: Lab 8.1 — Reproduce an Existing GitHub Issue. Then Lab 8.2 — Implement the Fix, Write the Test, Open the PR and Lab 8.3 — Improve Error Messages and Diagnostics. The graded, end-to-end version of this loop is the Capstone; the difficulty-ordered map of contribution types is the issue roadmap.