diff --git a/COMMS/sidecar-app-store/14-claude-installs-and-runs.md b/COMMS/sidecar-app-store/14-claude-installs-and-runs.md new file mode 100644 index 00000000..1a19c5bf --- /dev/null +++ b/COMMS/sidecar-app-store/14-claude-installs-and-runs.md @@ -0,0 +1,116 @@ +# 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`.