SchOS/.claude/memory/schunix-build-speed.md
Nexoniarz 3182da3965 Carry project memory in the repo, with tools/memory-sync.sh
Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:

  - D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
    invisible until moved across by hand.
  - Development happens from Windows and from Linux, and each looks in a
    different place for the same checkout.

Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.

The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
2026-08-09 14:27:22 +02:00

2.6 KiB

name description metadata
schunix-build-speed The build is fast now (no-op ~2s); the four things that made it slow and the traps that will make it slow again
node_type type originSessionId modified
memory project a665e14b-86bd-4b41-a228-51377f3e9225 2026-08-04T17:27:56.099Z

A no-op make went from 1m48s to ~2s on 2026-08-04, and a full kernel rebuild from ~110s to ~7s. Almost none of it was compilation — with only 2.4s of CPU in that 1m48, it was I/O across WSL's /mnt/d boundary. Four causes, all still worth not reintroducing:

  • tools/mkfs built the image on the file, one lseek + one 512-byte write per block: 300,000 syscall pairs, 150,000 of them just to zero it. It now assembles the image in RAM and writes once. It also stops at the last non-zero block and ftruncates, so ~2 MB is written instead of 147 MB.
  • The image was rebuilt on every invocation. sysroot-install is phony, so everything depending on it always ran. There is now a build/sysroot.stamp whose mtime only advances when a find-based summary of the staged tree actually changes (the cmp -s ... || mv idiom — do not add a touch, and note touch -c still updates an existing file's mtime, which is exactly the bug I wrote and had to remove).
  • ports/toybox re-linked its 236 applets every build, because port.mk declares install .PHONY for every port. It hangs off $(BUILDDIR)/toybox.installed now. This was the sneaky one: it bumped the mtime of 236 files in sysroot/ and so invalidated the stamp above.
  • mkfs and mkschfs re-read every file to dedup it. They now check (st_dev, st_ino) first — toybox's applets are real hard links, so 236 of them are settled by one fstat instead of ~500 MB of reads.

Two real bugs found in the Makefile while doing this:

  • $(BUILD)/kernel/**/*.d only matched one directory level. make's wildcard has no recursive **. arch/x86_64/ and fs/schfs/ therefore had no header dependency tracking at all — editing include/schfs.h left stale objects that linked fine. It uses $(shell find ...) now.
  • CFLAGS is recursively expanded and had a $(shell) appended, so the stack-protector probe re-ran on every expansion: one extra gcc per object file. Probed once into NO_SSP := now.

schunix.img is no longer .PHONY (it is a real file), and MAKEFLAGS += -j$(nproc) is on by default — use make -j1 for readable output from a failing build.

Verify with make schfs-check plus schunix-verify-by-driving-shell; the superblock checksum is printed by mkfs and should not change when only build mechanics change.