fix(storage): stop the wide usage profiles from OOMKilling the container #27
No reviewers
Labels
No labels
burndown-2026-08
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/advocate
role/director
role/exec
role/frontend
role/gamedev
role/human
role/platform
role/qa
role/science
role/sysadmin
state
ambient
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/node-stats-mcp!27
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/uq98-scan-memory"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes part of #26 (root causes A, B, and C).
Root cause A - the OOM is structural, not a budget mismatch
_scan_treekept every scanned file inode in its dedup set, so the set grew with the tree rather than with hard links. Measured locally: 162 MB per million(dev, ino)tuples, so the configured 5,000,000-entry budget needs roughly 810 MB for the dedup set alone, before any traversal state, against a 512Mi limit. That is whyroot,var, andvar-libdied at four minutes every time with exit 137 while the bounded profiles finished fine.The fix removes the term rather than re-tuning around it:
st_nlink > 1. Only a multiply-linked file can be reached twice, and same-filesystem bind and subtree mounts are already excluded by_mount_exclusions. This is the linedudraws and it leaves the documented hard-link contract inhost-storage-snapshot.mdunchanged. Live evidence: the currentk3s-storagesnapshot reportsdeduplicated_entries: 202across 344,464 entries, all of it in the Docker layer store.strrather thanPath- 153 vs 362 bytes per entry.hardlinked_inodes_trackedso the remaining cost is observable rather than inferred.Config and limit now agree because the scan fits, so no
deploychange is proposed for the limit. If a liverootscan still dies, that is new evidence and a different fix.Root cause C - the pod-ephemeral domain
Adds a
pod-ephemeralprofile at/var/lib/kubelet/pods. Mount exclusion already drops the local-path PVCs kubelet bind-mounts into that tree, so the profile does not re-countk3s-storagebytes - pinned by a new test. Nothing is needed indeploy: it sets noNODE_STATS_HOST_USAGE_PROFILESoverride, so the new default ships with the image.Root cause B - please read before merging
I did not add
/var/lib/kubelettoNODE_STATS_READABLE_ROOTS, and I recommend against it. That allowlist gatesread_text_headas well asstat_paththrough the shared_resolve_readable./var/lib/kubelet/pods/*/volumes/kubernetes.io~secret/and~projected/hold decrypted Kubernetes Secrets and service-account tokens as plaintext files, so granting the root would make every mounted secret on the node readable through an MCP tool call. That contradicts this repo's ownAGENTS.mdsafety rule.The
pod-ephemeralprofile answers what the incident actually needed - byte attribution under that tree - without any file-read grant. If path-levelstatthere is still wanted, the clean shape is a separate stat-only allowlist rather than widening this one, and that is worth its own issue.Verification
ward exec lint,ward exec test(46 passed, 2 new), andward exec precommitare all clean.Opened as a PR rather than pushed to
main: themerge-remote-mainworkflow applies, but the direct push tomainwas refused by the harness, so this branch is the durable checkpoint.🤖 Generated with Claude Code