μοῖρα • a portion, one's allotted share
From μείρομαι (meíromai, "to receive as one's portion"), from Proto-Indo-European *(s)mer- ("to allot")
Pronunciation: /ˈmoi̯.ra/
moira is du for APFS. On APFS, cp -c and GNU cp (reflink by default since
coreutils 9) clone files, build caches clone their artifacts, and Time Machine
keeps hourly local snapshots, so the number du prints is not what deleting a directory
frees. du charges every clone in full and says nothing about snapshots.
moira charges each file its share of the blocks it holds. Clones split what they still have in common, a rewritten clone owns its rewritten blocks, and hard links split their inode. Summed over a volume, the shares equal the space the volume uses. Alongside the share, moira reports what deletion frees and how much of that a snapshot still holds.
It reads the APFS extended attributes (getattrlist(2)) in one pass and keeps no state
between runs.
moira runs on macOS. Install a release with mise:
mise use -g github:srnnkls/moiraOr build from source with Zig 0.16.0 (pinned in mise.toml):
mise exec -- zig build -Doptimize=ReleaseSafe --prefix ~/.localTo use it in place of du in zsh:
(( $+commands[moira] )) && alias du=moiraAgent shells such as Claude Code's inherit zshrc aliases, so agents running du get moira
too; the fas rule tells them about -S, -E and -p.
moira is du: same flags, same output. One more letter switches the number to what APFS
actually holds, in the same shape. For a directory, -sh prints what du prints, -Ssh its
fair share of shared blocks, and -Esh what deleting it frees:
moira -sh build
moira -Ssh build
moira -Esh build-C prints every metric side by side:
$ moira -C -k -d1 clones
allocated share exclusive pinned path
2048 1024 0 0 clones/full
2048 1280 512 0 clones/partial
4096 2304 512 0 clonesHere full is a clone pair and partial a pair with 256 KiB of one side rewritten. du
reports 4096 KiB for both; they occupy 2304.
On a terminal, a run that met shared blocks says so on stderr:
$ moira -sh clones
4.0M clones
moira: 1.8M of 4.0M is shared through clones or hard links; -S shows each entry's share, -C every metric| Flag | Column | Meaning |
|---|---|---|
| allocated | what du reports (default) |
|
-S, --share |
share | the entry's portion of blocks shared through clones or hard links |
-E, --exclusive |
exclusive | blocks no other entry holds; freed by deletion once no snapshot holds them |
-p, --pinned |
pinned | the part of exclusive a snapshot holds; freed when the snapshot expires |
Files deleted earlier appear in no column: the snapshot that still holds them has no path to
walk. tmutil listlocalsnapshots / lists the snapshots; removing them returns the space.
APFS reports sharing per file, not per block. A clone pair whose blocks were rewritten before the latest snapshot is counted as pinned rather than shared, and blocks shared by some but not all clones are split as if every clone held them.
moira prints what macOS du prints across -a -s -d -c -k -m -g -h --si -A -B -l -I -t -x -H -L -P and BLOCKSIZE; tests/scrut/du-parity.md checks each against the system du.
Its own letters, -S -E -p -C, are unused by du, which keeps the alias
safe. One deliberate difference: du prints nothing for a negative -t, contrary to its
manual; moira shows the directories smaller than the threshold.
Coding agents reach for du and trust its numbers, and the terminal hint never reaches them.
moira ships a
fas rule, rules/fas/du.cue, that answers every du
an agent runs with what du misses on APFS and the moira flag that covers it:
du_hint: {
when: hook.#PreToolUse & tool.#Bash & (bash.#command & {#name: "du"})
then: inject: {
rule_id: "du-apfs-hint"
channel: "agent"
text: "HINT: On APFS `du` charges every clone and hard link in full ..."
}
}The rule is offered through phora. Bind it into a fas rules directory:
[sources.moira]
git = "https://github.com/srnnkls/moira.git"
branch = "main"
root = "rules/fas"
[targets.fas-rules]
path = "~/.config/fas/rules/moira"
sources.moira = { collapse = false }moira reads each directory with one getattrlistbulk(2) call and requests only the
attributes the printed metric needs; the terminal hint adds the clone flags. Warm cache, hyperfine, Apple silicon, macOS 26.5:
| Command | 38k files | 130k files |
|---|---|---|
du -s |
192 ms | 1589 ms |
moira -s, piped |
137 ms | 1208 ms |
moira -S -s |
166 ms | 1353 ms |
moira -C -s |
314 ms | 2128 ms |
The table reads every file's private size for the pinned column, which makes APFS walk
the file's extents. mise run bench <dir>... times the same commands on your own trees.
mise run test runs every suite; any combination of --fmt, --unit and --integration
selects some:
mise run test
mise run test --unit --integration