This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a learning/educational project. The primary goal is understanding OS internals, not shipping quickly.
Every code change must be explained in detail:
- What the code does and why it's needed
- How it interacts with hardware or other kernel subsystems
- Why this approach was chosen over alternatives
- Any relevant x86_64, OS theory, or low-level concepts involved
Slower progress with thorough explanations is preferred over fast, unexplained changes.
Do not write logically-significant portions directly. Instead:
- Discuss the problem and potential approaches
- Explain relevant concepts, tradeoffs, and gotchas
- Let the user implement the fix themselves
- Review the user's implementation and provide feedback
This maximizes learning value. Writing pertinent code for the user bypasses the educational benefit of working through the implementation details. The user learns more by:
- Understanding the problem through discussion
- Reasoning about the solution approach
- Writing the code themselves
- Getting feedback on their implementation
Exception: Trivial changes, debugging (e.g log lines), reading files (you don't need to ask to see things, just read the files yourself), user-directed bulk refactoring tasks, and structural changes that don't impact any logic. Also very simple logic like one-liner functions can be added directly.
You may also run the project yourself as needed to understand what's going on.
Cottage is an experimental x86_64 operating system with a kernel-space web server. The goal is an OS whose userspace is created primarily using JavaScript running in a browser. Written in C (GNU23) and x86_64 assembly.
make all # Build ISO image (cottage.iso)
make all-hdd # Build HDD image (cottage.hdd)
make run-uefi # Run in QEMU with UEFI (recommended)
make run # Run in QEMU with BIOS
make test # Alias for run-uefi
make test-headless # Run headless (no GUI), for CI/automated testing
make test-headless TIMEOUT=30 # Run headless with 30 second timeout
make clean # Clean build artifacts
make distclean # Full clean including dependencies (limine, ovmf)
make ovmf # Download nightly OVMF firmwareYou have no sense of elapsed time during command execution. When you see test output followed by a timeout message, you cannot tell whether:
- The system was still making progress when killed (timeout too short), OR
- The system stalled early and sat frozen until the timeout (actual bug)
Example: A 15-second timeout test might produce output for 2 seconds, then freeze for 13 seconds before being killed. You would see the same result as if it ran for 14.9 seconds and got cut off.
Rules for debugging hangs/stalls:
- Never assume a longer timeout will help - ask the user if output was still flowing when it stopped
- Never retry with a longer timeout without user input about timing
- Ask the user to describe timing behavior: "Did it stall immediately after X, or was it still producing output?"
- If adding instrumentation, use periodic "heartbeat" logs so stalls become obvious in the output
- A "successful boot" requires specific log entries - ask if unsure what constitutes success
For automated testing or debugging, use make test-headless TIMEOUT=<seconds>:
- Output goes to stdout via serial console
- QEMU exits after the timeout (exit code 124 from
timeoutcommand, but make target succeeds) - Without TIMEOUT, runs until crash or Ctrl+C
Debug build: COTTAGE_DEBUG=1 make all
When to use parallel testing:
- Debugging intermittent failures ("works sometimes", "fails randomly")
- Working on scheduler, SMP, or locking code
- After fixing a race condition, to verify the fix is effective
- Establishing a baseline failure rate before/after changes
How to run:
make test-parallel # 20 runs, auto-detect parallelism
make test-parallel RUNS=50 # More runs for better statistics
make test-parallel RUNS=30 CPUS=1 # Single-core VMs (no SMP races)
./scripts/test-parallel.sh -v # Verbose mode (show failures)
./scripts/test-parallel.sh -p "panic" # Custom pattern (look for panics)
./scripts/test-parallel.sh -s /tmp/cottage-failures # Save failure logs to /tmp/cottage-failuresInterpreting results:
- 95-100%: System is stable for this workload
- 80-95%: Intermittent race condition present
- <80%: Significant stability issue or test environment problem
- Compare single-core (
CPUS=1) vs dual-core (CPUS=2) to isolate SMP-specific races
Important: The script auto-detects parallelism to avoid overcommitting host CPUs. Running too many VMs causes host contention which produces misleading failure rates.
The Makefile auto-detects CPU cores for parallel compilation. Override with make JOBS=4 if needed.
The kernel is organized into subsystems:
- Memory:
pmm/(physical),vmm/(virtual),malloc/,slab/ - CPU/Interrupts:
gdt/,idt/,isr/,apic/,smp/ - Scheduling:
sched/- basic scheduler, ~512 max threads, 2MB stack per thread - Filesystem:
vfs/,tmpfs/,devtmpfs/(ext2 WIP) - Drivers:
e1000/(network),flanterm/(terminal) - Syscalls:
syscall/(infrastructure present, implementation WIP) - ACPI:
acpi/,lai/(LAI interpreter submodule)
kernel/src/main.c: Kernel entry (_start) - initializes framebuffer, DTB, PMM, GDT, IDT, VMM, ACPI, launches schedulerinit/src/main.c: Init process stub (early stage)
- Higher-half kernel mapped at
0xffffffff80000000 - Paging mandatory (enforced by Limine bootloader)
- KASLR supported but disabled by default
kernel/: Kernel source and buildinit/: Init process/initramfslimine.cfg: Bootloader configurationkernel/linker.ld: Kernel linker script
- 4-space indentation, LF line endings, 120 char max line length
- Next-line brace style, spaces around operators
- Compiled with
-Werror(warnings as errors)
-
Return values: Any pointer returned from a function must point to heap-allocated memory or NULL (never stack/static). Must be safe to
free(). -
String pointers in structs: Always copy strings into structures with their own allocated memory. Free when structure lifetime ends.
master: Production branch for nightly builds, must stay stabledevelop: Integration branch for PRs- Feature branches target
develop, nevermasterdirectly - PRs require an associated GitHub issue first
- Limine v5.x bootloader (cloned during build)
- LAI (ACPI interpreter) as git submodule at
kernel/src/lai/ - OVMF firmware for UEFI testing (optional, downloaded via
make ovmf) - Build tools: clang/gcc, nasm, xorriso, git, curl