# 14 — `provisionClaudeCli` works, the mask is fixed, and one strip is still no-opping Commit read: `ef000aaf` (`52b021bb..ef000aaf`). Verified against a real reprovision of green on the production host, uid 1001, at 00:02 UTC. **Both your fixes work. Two of the three failures from `12` are closed, and the third closed as a side effect.** --- ## Working, measured **1. `provisionClaudeCli` — the first successful execution anywhere.** ``` ~/.local/bin/claude -> …/.local/share/claude/versions/2.1.228 owned by green run as the member: 2.1.228 (Claude Code) ``` The largest untested assumption in the feature is no longer untested. Note the shape: it is a **symlink into a versions directory**, on a member's install as on the owner's — which retroactively justifies `sameFile` and `realpathSync` in `spawn-as-member.ts`. The string compare that shipped first would have thrown on every member turn. **2. The file browser works.** `mask::rwx` on the home, and the platform can list it. So the clamp **was** in that block — your `.local` fix moved it, exactly as you predicted in `13`. Nothing further to bisect. **3. Rootless Docker runs.** Server 29.2.1 as the member, and the isolation holds: green sees **0** containers while the owner has 8. A container starts and writes into a bind mount under `~/.local/dockers`. `~/.claude/.credentials.json` is correctly absent — nobody has signed in, and now there is finally a binary to sign in with. --- ## Still open ### A. `provisionRootlessDocker`'s ACL strip no-opped — and this is the first-review gap `~/.local/share/docker` came out of provisioning carrying the home's inherited defaults: ``` mask::--x default:user:pastilhas:rwx default:group::--- default:other::--- ← should have been stripped ``` `os-user-docker.ts` guards the strip with `existsSync(dockerData)`. On this account the daemon **failed to start on the first provision** — `.local` was root-owned then — so Docker never created that directory, the guard was false, and the strip did nothing. The retry started the daemon, which created the directory fresh with the defaults inherited, and by then the strip had already run or raced it. This is the gap flagged in my very first review of the night: > *"`existsSync(dockerData)` is false on a first run where the daemon hasn't yet created the dir, so the strip > can no-op, with retry the only cure."* It is worse than "retry cures it", because the retry is what *creates* the directory. **Suggested fix, mirroring what you just did for `.local`:** create `~/.local/share/docker` explicitly with the member's ownership and no default ACLs **before** starting the daemon, so Docker inherits a clean directory rather than being chased afterwards. A guard that depends on another process having got there first is a race however it is written. ### B. `.local` is unreadable by the platform — the prediction in `12` held ``` user:pastilhas:rwx #effective:--- mask::--- ``` The ACL block runs at `os-user.ts:362`; `.local` is created at `:398`, after it, and the explicit `-m 700` clamps the mask on the entries inherited from the home's defaults. So the file browser can list a member's home and cannot open `.local`. Not urgent and arguably not wrong — `.local` holds binaries and container storage rather than a member's documents. But it is the same clamping mechanism as the home bug, one level down, and it should be a decision rather than a leftover. If the platform should see it, the ACL pass needs to run after the directories are created, not before. ### C. A container with an explicit non-root uid **and** a bind mount fails — not root-caused ``` docker run -u 70 alpine id → uid=70 OK docker run -v …/probe:/data alpine (root) → BIND_WRITE_OK docker run -u 70 -v …/probe:/data alpine → sh: id: Permission denied ``` Only the combination fails. I tried two hypotheses and **both were wrong**: stripping the default ACLs from `~/.local/share/docker` did not fix it, and opening `.local` to 711 did not either. I restored both. I am reporting this as an observation rather than a defect, because it may be an artefact of my probe: alpine has no uid 70 in `/etc/passwd`, and forcing one may break the container for reasons unrelated to permissions. **The meaningful test is a real `postgres:18-alpine` with a bind mount**, which is the case that failed and then worked on the previous green earlier tonight. I have not run it on this account. Worth doing before anyone concludes bind mounts are healthy. ### D. Minor: every file in a member's home looks executable ``` -rw-rwx--- …/.config/systemd/user/docker.service systemd: "Configuration file … is marked executable. Please remove executable permission bits. Proceeding anyway." ``` The `x` in the group triad is the **ACL mask**, not a real group permission — but anything that inspects mode bits reads it as executable. systemd only warns. Something stricter would refuse: `ssh` rejecting a key file on loose permissions is the obvious candidate, and `.ssh` only escapes it because `os-user-ssh.ts` installs those files with explicit modes. Worth knowing before the next tool starts complaining. --- ## State Green: account, ACLs, SSH, shell, Claude and rootless Docker all provisioned. Both gates unchanged, `member` populated by nothing, the six bare-`sessionKey` commands from `11` still unguarded. The next thing that would move the feature is a member signing in with their own Anthropic account — which is now possible for the first time, and which is also the untested OAuth-in-a-web-terminal flow from `10`.