Uh oh!
There was an error while loading. Please reload this page.
fix(chart): stop init-writable-data skipping the chmod when the chown fails (#672) - #689
Conversation
… fails (#672) init-writable-data ran `chown 1000:1000 "$d" && chmod "$m" "$d" || echo …`, so a refused chown short-circuited the chmod and the mode was never applied — while the message said "leaving as-is", implying nothing could be done. That inverts the priority. kubelet ignores fsGroup on hostPath (kubernetes/kubernetes#138411), so the MODE is what makes these trees usable: /data/shared must be other-writable for the ingestion Job (uid 65534, or HOST_UID) and the CLI staging/teardown pod (uid 65532), neither of which is 1000 nor shares a group with it. The chown is cosmetic next to that, and it is also the call most likely to be refused — on a Windows/Docker-Desktop bind mount or an NFS root_squash export it is precisely what fails. So the failure that mattered least was cancelling the one that mattered most, silently nullifying the 2777/3777 split from #667 on the platform that split was written for. Symptom: #653's `mkdir: can't create directory '/data/shared/.tracebloc-staging/': Permission denied`. The chown and the chmod are now separate best-effort statements, each recording whether it failed, and the per-dir verdict is graded on the mode OBSERVED afterwards via `ls -ldn` rather than on either exit status — a bind mount can accept a chmod and ignore it, so an exit code is not evidence. A partial result is reported as such ("chown failed; mode applied anyway") instead of implied. Unchanged: per-dir modes, per-dir independence, non-fatal behaviour, POSIX sh for busybox. Kept diffable by eye against the installer's Get-ReleaseDirsPrepCommand, which already does it this way. Verified by executing the helm-rendered command[2], not by reading it: - sh -n, dash -n, bash --posix -n all clean - busybox:1.35 as root: /data/shared drwxrwsrwx, /data/logs drwxrwsrwt, exit 0 - busybox:1.35 with --cap-drop CHOWN (chown refused, chmod permitted): modes STILL land drwxrwsrwx / drwxrwsrwt; the old command leaves both at drwxr-xr-x - /data/shared read-only (both calls fail): FAIL reported with the real errno, /data/logs still fixed, exit 0 - end-to-end on a shared volume after a refused chown: uid 65534 creates .tracebloc-staging and writes /data/logs; uid 65532 unlinks uid 65534's entries in /data/shared (no sticky) but not in /data/logs (sticky) — both splits intact Tests: the new #672 case fails against the old command and passes against the fix. The obvious comment-scoped guard (`^[^#\n]*chown.*&&.*chmod`) is silently VACUOUS — `${e#*:}` puts a '#' before the chown — so the guard is unscoped and the template describes the old shape in words instead. Existing assertions kept, updated for the multi-line command. jobs_manager_test.yaml 34 -> 35 passing; full suite 379 -> 380 passing with develop's 5 failed / 5 errored baseline unchanged. Also adds the recurring-finding rule to .cursor/BUGBOT.md per CLAUDE.md. Refs #672, #667, #653, #654 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two overclaims in the first commit's own reporting, both found by running the failure paths rather than reading them — the same family as the bug being fixed. 1. The verdict grades other-writability alone (correctly: that is what decides whether uid 65534/65532 can work, and failing a setgid-stripped-but-writable mount would cry wolf on a working install). But it labelled that bare "OK", which reads as "the whole mode landed". Now says "OK <dir> other-writable" and always prints want vs got, so a mount that granted other-write while dropping S_ISGID is visible instead of implied. 2. Worse: the partial-result note said "mode applied anyway" whenever any call failed. On a dir that was ALREADY other-writable and where BOTH calls were refused, that is simply false — nothing this container did applied anything. Reproduced in busybox:1.35 (pre-set 1777, run as a non-owner uid so chown and chmod are both refused): want 2777 got drwxrwxrwt uid 0 (chown+chmod failed; mode applied anyway) Now reads "(chown+chmod failed; other-writable regardless)" — it claims the observation, not a causal link it cannot support. Re-verified on the helm-rendered command[2]: sh -n / dash -n / bash --posix -n clean; root happy path lands drwxrwsrwx + drwxrwsrwt; chown-refused still lands both modes; already-1777 with both calls refused now reports truthfully; read-only /data/shared still FAILs with the real errno while /data/logs is still fixed; exit 0 throughout. Tests pin both strings, including a notMatchRegex on the old "mode applied anyway" wording. 35 passing, full suite 380 with develop's 5 failed / 5 errored baseline unchanged. Refs #672 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…672) The long "why other-writability alone is the pass condition" prose was inside the script passed to `sh -c`, so it shipped in the pod spec and showed up in every `kubectl get deploy -o yaml`. It belongs in the YAML comment above, which does not. Left a two-line pointer where a script editor will see it. Also records the one intentional divergence from the installer's Get-ReleaseDirsPrepCommand: the chart does not redirect chown/chmod stderr to /dev/null, so the real errno (Operation not permitted vs Read-only file system) lands in `kubectl logs` next to the verdict. The installer suppresses it because its output is a user-facing progress line; an init container's log is a debugging surface, and hiding the errno there would remove the evidence a reader needs. Comment-only inside command[2]: re-rendered and re-ran the chown-refused path in busybox:1.35 to confirm byte-identical output and modes (drwxrwsrwx / drwxrwsrwt, exit 0). 35 passing; full suite 380 passing, baseline unchanged. Refs #672 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shujaatTracebloc
commented
Aug 12, 2026
bugbot run |
…ed (#672) The rule said "for two releases". Verified against git instead: the `chown … && chmod …` shape entered in chart 1.9.20 (#611/#612, commit a07f76b) and survived every version through 1.9.33 — thirteen chart versions, not two. #667 (7852f02) rewrote the modes on that exact line and left the chain untouched, which is the more useful half of the lesson: the line was re-read for its modes and not for its control flow. Also corrects the issue's attribution of the chain to #667. Refs #672, #667, #611 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit ae7471a. Configure here.
shujaatTracebloc
commented
Aug 12, 2026
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 4489aba. Configure here.
…ble-data-independent-chmod # Conflicts: # client/Chart.yaml
shujaatTracebloc
commented
Aug 13, 2026
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 56171e8. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
shujaatTracebloc
commented
Aug 13, 2026
Two corrections to the description above (post-merge)Leaving the body as-written rather than editing it — the verification lines record what actually ran at the time, and rewriting them would falsify that record. Correcting here instead. 1. The blast radius is wider than stated. The Notes say the chain "shipped in every version through 1.9.33 — thirteen chart versions". That was true when written; 2. The "Out of scope … Worth its own ticket" note is obsolete — please don't file that ticket. It flags the dirs the ingest creates inside
So the follow-up this PR asks for is done; acting on the note as written would duplicate a closed ticket using a diagnosis that's been disproven. Neither correction affects the change that merged — the chart fix itself is unchanged and stands. |
Fixes#672.
The bug
init-writable-datachained the two repairs:A refused
chownshort-circuits, so thechmodnever runs — and the message says"leaving as-is", which reads as "nothing could be done" when the one operation that
mattered was never attempted.
Why it's worse than a cosmetic ordering nit
The mode, not the ownership, is what makes these trees usable. kubelet ignores
fsGroupon hostPath (kubernetes/kubernetes#138411), so/data/sharedhas to beother-writable for the ingestion Job (uid 65534, or
HOST_UID) and the CLIstaging/teardown pod (uid 65532) — neither is 1000, and neither shares a group with it.
The
chownto 1000 is cosmetic next to that.It is also the call most likely to fail: on a Windows/Docker-Desktop bind mount or an
NFS
root_squashexport,chownis exactly what gets refused whilechmodwould havesucceeded. So the least important failure was cancelling the most important repair, which
silently nullified the 2777/3777 split from #667on the platform that split was written
for. Losing the chmod is strictly worse than losing the chown. Symptom is #653's
mkdir: can't create directory '/data/shared/.tracebloc-staging/': Permission denied.The fix
chownandchmodare now separate best-effort statements, each recording whether itfailed, and the per-dir verdict is graded on the mode observed afterwards (
ls -ldn)rather than on either exit status — a bind mount can accept a
chmodand ignore it, so anexit code is not evidence. A partial result is reported as one:
The verdict grades other-writability alone — that is what decides whether those uids can
work, and failing a mount that granted other-write while dropping
S_ISGIDwould cry wolf ona working install. So the label names the one thing it verified and always prints
wantvsgot, rather than a bare "OK" that could be read as "the whole mode landed".stderr from the failing call is deliberately not suppressed, so the real errno
(
Operation not permittedvsRead-only file system) lands inkubectl logsnext to theverdict. That is the one intentional divergence from
Get-ReleaseDirsPrepCommand, whichsuppresses it because its output is a user-facing installer line.
Unchanged: per-dir modes (
/data/shared2777 setgid-no-sticky,/data/logs3777setgid+sticky), per-dir independence, non-fatal behaviour, POSIX sh for busybox. Kept
diffable by eye against the installer's
Get-ReleaseDirsPrepCommand, which already usesthe same
path:modespec and the samels -ldnverification.Verified by executing the rendered command, not by reading it
Every run below uses
command[2]extracted fromhelm templateoutput, inbusybox:1.35.sh -n,dash -n,bash --posix -n/data/shareddrwxrwsrwx,/data/logsdrwxrwsrwt, uid 1000, exit 0--cap-drop CHOWN(chown refused, chmod permitted)drwxrwsrwx/drwxrwsrwt, exit 0drwxr-xr-xwhile logging "leaving as-is"/data/sharedread-only (both calls fail)FAILwith the real errno;/data/logsstill fixed; exit 0End-to-end on a shared volume, after a refused chown: uid 65534 creates
.tracebloc-staging/and writes/data/logs; uid 65532 unlinks uid 65534's entries in/data/shared(no sticky) but is correctly refused in/data/logs(sticky). Both halvesof the #667 split still hold.
Tests
New
#672case inclient/tests/jobs_manager_test.yaml, checked in both directions: itfails against the old command and passes against the fix. Existing assertions kept and
updated for the multi-line command (
(?s)where a pattern now spans lines).One trap worth flagging for review: the obvious comment-scoped guard
^[^#\n]*chown.*&&.*chmodis silently vacuous here — the loop body legitimatelycontains a
#(${e#*:}) before the chown, so[^#\n]*can never reach it and theassertion passes against the very one-liner it exists to reject. I hit this and confirmed
it by running the assertion against the old command. The guard is therefore unscoped, and
the template describes the old shape in words ("chained with a logical AND") rather than in
code so prose can't trip it.
helm unittest client -f 'tests/jobs_manager_test.yaml': 35 passed (was 34)suites is unchanged — verified against a clean
origin/developcheckout, not assumedhelm lint: unchanged (pre-existingclientId/clientPasswordminLengtherrors only)scripts/chart-version-guard.shwithBASE_SHA=origin/develop: ✓ (1.9.33 → 1.9.34,version+appVersionin lockstep)Second commit: two overclaims in my own reporting
Found by running the failure paths, not reading them — same family as the bug being fixed, so
worth calling out rather than burying:
The pass label was a bare
OK, which reads as "the whole mode landed" when onlyother-writability was checked. Now
OK <dir> other-writable, want … got ….Worse: the partial-result note said "mode applied anyway" whenever a call failed. On a
dir that was already other-writable where both calls were refused, that is false —
nothing the container did applied anything. Reproduced in
busybox:1.35(pre-set1777,run as a non-owner uid so both calls are refused):
Now reads
(chown+chmod failed; other-writable regardless)— the observation, not a causalclaim it cannot support. A
notMatchRegexpins the old wording out.Notes
The bug is older than the issue says.init-writable-data skips the chmod whenever the chown fails (chown && chmod) #672 attributes the chain to fix(chart): drop the sticky bit on /data/shared so a dataset can be deleted #667. Git says
otherwise:
chown … && chmod …entered in chart 1.9.20 (Truncated tool binary passes #607's size floor and dead-ends at the checksum (make the checksum drive retry) #611/fix(installer): harden the Windows install→first-experiment path — checksum-driven downloads, reliable cluster-create, writable ingest & training volumes, local-chart support (#611) #612,a07f76b) andshipped in every version through 1.9.33 — thirteen chart versions, not just since fix(chart): drop the sticky bit on /data/shared so a dataset can be deleted #667.
fix(chart): drop the sticky bit on /data/shared so a dataset can be deleted #667 (
7852f02) rewrote the modes on that exact line and left the control flow alone.That widens the blast radius: any hostPath edge installed since 1.9.20 on a mount that
refuses
chownhas had a 0755/data/sharedthis whole time.scripts/install-k8s.ps1deliberately untouched — fix(installer): pre-create the hostPath PV dirs on Windows so the first ingest works #654 is open against it and alreadydoes chown/chmod as separate statements. Chart-only change.
Adds the recurring-finding rule to
.cursor/BUGBOT.md, per CLAUDE.md ("a finding thatrecurs across PRs becomes a rule"). This shape has now appeared in fix(installer): pre-create the hostPath PV dirs on Windows so the first ingest works #654 (four findings of
the same family) and here. The grep-expressible half belongs in code-quality's
house-rules, which live in
tracebloc/.github— out of scope for this PR.Fixed a dangling clause in the neighbouring comment block ("…clean up after them; runs as
root only long enough to fix the mounts") that had lost its subject in an earlier edit.
Out of scope, spotted while verifying: the dirs the ingest itself creates inside
/data/sharedcome outdrwxr-sr-x(its own umask), so uid 65532 cannot delete filesnested inside them — only entries directly in
/data/shared. The 2777-no-sticky choicefixes the top level only. Not a regression from this PR (on develop the ingest cannot
create the staging dir at all when the chown is refused), and it depends on the ingestor's
umask, which lives in another repo. Worth its own ticket.
🤖 Generated with Claude Code
Note
Medium Risk
Changes privileged init behavior on every hostPath jobs-manager rollout; impact is positive for common mount types but alters logging and permission repair semantics at cluster bootstrap.
Overview
Fixes#672: on hostPath installs,
init-writable-datano longer chainschownandchmodwith&&, so a refusedchown(bind mounts, NFSroot_squash, etc.) no longer skips the load-bearingchmodthat makes/data/sharedand/data/logsusable for non-1000 writers.The init script is now a multi-line shell block: separate best-effort
chownandchmod, per-dir OK/FAIL lines graded on observed mode vials -ldn(other-write only, with explicitwantvsgot), alwaysexit 0, and stderr left visible in pod logs. Template comments document the control-flow bug and alignment with the PowerShell installer prep path.Chart bumps to 1.9.37 (
version/appVersion). Tests update existing#611assertions for the new script shape and add a dedicated#672case (nochown…&&…chmod,ls -ldncheck, nomode applied anyway)..cursor/BUGBOT.mdadds a standing rule to flag independent repairs joined by&&when the second operation matters.Reviewed by Cursor Bugbot for commit 56171e8. Bugbot is set up for automated code reviews on this repo. Configure here.