diff --git a/CLAUDE.md b/CLAUDE.md index 058551e..ee5e882 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -384,6 +384,15 @@ reverse proxy or ingress in front of the plain-HTTP port. `//go:build gosd` — and why `make check` also compiles that tagged side for linux/arm64 (`vet-gosd`; CI mirrors it in test.yml), so it can't rot unnoticed between gosd builds the way a purely gosd-side file could. +- **CI's test user walks straight through `chmod`.** A test that blocks a + write by making its directory read-only passes locally and then fails on + Tangled, where the permission check is bypassed as it is for root — which + is how `go test -race ./...` first went red on a green branch. Block a + write with something the kernel refuses regardless of user instead (a + directory sitting where the file has to be renamed into place, say); + `t.Skip` under `os.Geteuid() == 0` is the fallback for a test that is + genuinely *about* permissions (internal/daemon's probeWritable), and it + costs that test's coverage in CI entirely. - **The Bash tool's working directory persists between calls** — `cd` back to the repo (or use absolute paths) before git/beans commands, or they fail confusingly from a scratch dir. Also: zsh mangles bare words starting with diff --git a/internal/ipfs/unixfs_test.go b/internal/ipfs/unixfs_test.go index 48abd19..49cef1d 100644 --- a/internal/ipfs/unixfs_test.go +++ b/internal/ipfs/unixfs_test.go @@ -853,7 +853,7 @@ func TestDurableDirBatch_SyncsInFirstTouchOrderNotSorted(t *testing.T) { // the correctness bar this bean sets for candidate (a): batching the // directory fsync must not weaken what persist already guaranteed. Node // blocks are written (and, per the fix, dir-synced as a batch) before the -// leaf loop runs; blocking the leaves directory then forces persist to +// leaf loop runs; blocking a leaf's path then forces persist to // return an error partway through, and the already-written nodes must // still be there afterwards, with no manifest — the same "looks merely // unindexed" state persist's own doc comment promises for a crash @@ -876,14 +876,17 @@ func TestPersist_InterruptedAdoptionKeepsAlreadyWrittenArtifactsAndSelfHeals(t * } m.Adopted = true // exercises the durable batching path this bean changes - leavesDir := filepath.Join(ix.dir, leavesSubdir) - if err := os.Chmod(leavesDir, 0o555); err != nil { - t.Fatalf("chmod leaves dir read-only: %v", err) + // A read-only leaves directory was the obvious block, but CI's test user + // bypasses that permission check the way root does, and persist simply + // succeeded there. A directory sitting where a leaf file has to be + // renamed into place fails that rename for every user instead. + blocked := ix.leafPath(m.Leaves[0].CID) + if err := os.Mkdir(blocked, 0o755); err != nil { + t.Fatalf("blocking the first leaf's path: %v", err) } - t.Cleanup(func() { os.Chmod(leavesDir, 0o755) }) if err := ix.persist(res.CID, m, nodes); err == nil { - t.Fatal("persist succeeded with an unwritable leaves directory, want an error") + t.Fatal("persist succeeded with an unwritable leaf path, want an error") } for _, node := range m.Nodes { @@ -895,8 +898,8 @@ func TestPersist_InterruptedAdoptionKeepsAlreadyWrittenArtifactsAndSelfHeals(t * t.Errorf("Manifest after interrupted persist: ok=%v err=%v, want none written yet", ok, err) } - if err := os.Chmod(leavesDir, 0o755); err != nil { - t.Fatalf("restoring leaves dir permissions: %v", err) + if err := os.Remove(blocked); err != nil { + t.Fatalf("clearing the blocked leaf path: %v", err) } if err := ix.persist(res.CID, m, nodes); err != nil { t.Fatalf("retrying persist after clearing the block: %v", err)