Skip to content

tests: fixtures get the physical temporary folder on macOS - #2033

Merged
AbirAbbas merged 6 commits into
app/v3from
test/canonical-temp-roots
Nov 2, 2026
Merged

AbirAbbas merged 6 commits into
app/v3from
test/canonical-temp-roots

Conversation

@AbirAbbas

@AbirAbbas AbirAbbas commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Why

On macOS every process gets TMPDIR=/var/folders/…/T/, and /var is a link to /private/var. A Mac run of the full tree on app/v3 12dadd6 (macOS 15.6, Go 1.26.5) found 101 tests in nine packages that fail every time for that reason alone. Some compare a raw t.TempDir() with a path the product resolved. Others hit the product's deliberate "workspace binding traverses a symbolic link" refusal. All 101 pass when the test binary starts with a link-free TMPDIR. Linux has no such alias, so CI never sees this.

What changed (tests only; no product code, no Makefile)

  • New internal/testutil.CanonicalizeTempRoots, for macOS only. It resolves an absolute TMPDIR and GOTMPDIR with filepath.EvalSymlinks, following links before any lexical cleaning. It rewrites a variable only when the physical spelling differs.
    • Empty, unset and relative roots are left exactly as they are.
    • A root that doesn't exist stops the suite with the variable's name.
    • On every other system it returns before touching the filesystem or the environment.
  • TestMain calls it first in each of the nine packages:
    • new testmain_test.go files in workspaceidentity, taskcopy, syncsetup, cellstore and handoff;
    • the existing entry points of session, tui3, cmd/codeaf and run, after any child-process dispatch.
  • tui3 reads its scratch roots during package initialisation, before TestMain runs. Its TestMain now refreshes that root, so the root matches the fixtures.
  • Tests for the helper:
    • the alias with a trailing slash, separate TMPDIR/GOTMPDIR targets, empty and unset values, physical spellings left byte for byte, a missing target, a .. after a link, and relative roots;
    • a test that pins "untouched" on Linux and Windows.

What this does not fix. This is a fixture workaround. The 101 passing tests show that the fixtures now agree with the product. They do not show that the product handles aliases, and these packages no longer exercise /var aliases by accident. The product alias defects found while auditing this are being handled separately: alias-spelled roots in taskcopy/results.go:144-154, in cellstore canonicalTree for a missing child, in cmd/codeaf statFile/ListAgents, and in the prototype workspace-set doors. Tests that make their own links still cover those deliberate refusals.

How it was checked (this Mac)

app/v3 12dadd6 this branch
the 101 "TMPDIR is a symlink" tests, run per package with the ordinary macOS TMPDIR 101 fail 100 pass on the second run, 101 pass on the first; the odd one is the flake below
./internal/testutil n/a all pass (the non-darwin test skips here and runs on Linux CI)
  • go vet passes for the ten touched packages, both darwin and GOOS=linux, and ./internal/testutil also with GOOS=windows. gofmt is clean.
  • Flake now visible. TestTasklessTakePreservesConversationWorkspaceIdentity (syncsetup) used to fail before this point, at the symlink refusal. Now it fails about half the time with "taskless takeover unexpectedly called a provider": 2 of 6 runs on this branch, and 3 of 6 on app/v3 with a physical TMPDIR exported from outside. Its stub counts every HTTP request, and session.New starts a lane beat (internal/session/agent.go:511) whose first round can fetch endpoint metadata before Close. That race has nothing to do with this change and is left for a follow-up.
  • On the current app/v3 (1c4ec8a, after pair: merge a computer's own chats when it joins, instead of replacing them #2031), a local merge of this branch with tests: sync fixtures keep their socket homes short on macOS and wait for branch publication #2032 and the rebased syncsetup: a window displaced by a newer hold on the same computer lets go at once #2034 merges cleanly:
    • all 101 TMPDIR-class tests pass;
    • the whole testutil, workspaceidentity, handoff, run, taskcopy, cellstore and syncsetup packages show no failure beyond app/v3's own;
    • one exception: TestR9R23PersonalLearnerRefreshesColdRouterGeneration fails 3/3 on this Mac on pristine 1c4ec8a too, with either TMPDIR spelling. It is not this change, and it passed on 12dadd6.
  • CI, dispatched and judged by content against app/v3's own runs (Full check 40925415150 on 12dadd6 and 40940487776 on 1c4ec8a):
    • PR gate: the light-gate failures are the same tests with the same messages as app/v3's.
    • Full check: three attempts, each with two failures outside app/v3's list, and a different pair each time. They come from packages this change leaves alone. On Linux the helper returns before it reads anything, so these runs exercise exactly app/v3's code:
      • TestAWarmNoSendsBackToThePlanWithTheWordsAndAsksAgain (factory/worker; also seen on another branch's run);
      • TestAWithdrawnOrStaleTakeoverIsIgnored (session; it races the shared directory watch that answers takeover requests, and passes 10/10 here);
      • TestAppPresenceP1ReconnectReconcilesMissedOffline;
      • TestAppCrewProposalEndsInOneReceipt.
  • The Mac full-tree gate is below.

Codex adversarial review, three rounds:

  • Round 1:
    • The helper was not a strict no-op on Linux. It is now darwin-only.
    • Abs cleaned .. before links were followed. Links are now resolved first.
    • tui3's scratch roots were captured at initialisation. They are now refreshed.
  • Round 2:
    • Abs anchored a relative root to a logical working directory. Relative roots are now left alone.
    • The other-systems test needed link privilege on Windows. That case is now conditional.
    • The worry that cmd/codeaf might depend on tui3's scratch classification was left to the full-tree gate.
  • Round 3: approved, no findings.

Manual verification: none. This PR changes only tests; there is no app behaviour to exercise.


🤖 Generated with Claude Code

AbirAbbas added a commit that referenced this pull request Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 29, 2026
Its workspace host socket lives under that home. Under macOS's temporary
folder the address fit by a few bytes only while the folder was spelled
through the /var link; spelled physically (as #2033 makes fixtures), it is
too long and the host never listens. A /tmp home fits on every Unix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 29, 2026
Its workspace host socket lives under that home. Under macOS's temporary
folder the address fit by a few bytes only while the folder was spelled
through the /var link; spelled physically (as #2033 makes fixtures), it is
too long and the host never listens. A /tmp home fits on every Unix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the test/canonical-temp-roots branch from 129a69d to bc4d57d Compare October 29, 2026 23:41
@AbirAbbas

Copy link
Copy Markdown
Collaborator Author

Closing unmerged: the Mac push stopped early to save usage. What this fixes and what is left is tracked in #2050. The branch test/canonical-temp-roots is kept.

AbirAbbas and others added 6 commits November 2, 2026 10:45
macOS sets TMPDIR to /var/folders/.../T/, reached through the /var ->
/private/var link. 101 tests in nine packages compared a raw t.TempDir() with
a path the product resolved, or met the product's deliberate refusal of a
binding that traverses a link, and failed on every Mac run; all of them pass
when the test binary starts with a link-free TMPDIR.

internal/testutil.CanonicalizeTempRoots resolves a set TMPDIR and GOTMPDIR
and rewrites them only when the physical spelling differs, so it writes
nothing on Linux or wherever the folder is already physical. TestMain calls
it first in workspaceidentity, taskcopy, syncsetup, cellstore and handoff
(new), and in session, tui3, cmd/codeaf and run (after any child-process
dispatch). No product code or Makefile changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scratch roots follow

Review found three gaps. The helper also rewrote a linked, relative or
missing root on Linux, so it was not the no-op it claimed; it now returns at
once on every system but macOS, and a Linux test pins that. filepath.Abs
cleaned a ".." before links were followed, which could name another folder;
links are now resolved first. tui3 reads its scratch roots at package
initialisation, before TestMain, so they kept the alias while fixtures moved
to the physical folder; its TestMain refreshes that root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lias case

Review round 2: filepath.Abs anchored a relative root to the working
directory's logical spelling, which can name another folder; a relative root
is now left exactly as given (launchd always sets an absolute TMPDIR). The
other-systems test no longer needs link privileges on Windows: its alias case
runs only where a link can be made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestRemovedComputerReopensWithTheSameProjectLabel/project checks the hint
row for "project: " and the chat's t.TempDir(). With the physical
/private/var/folders spelling this PR gives tui3's TestMain, that folder,
named after the test, is longer than a 160-column row leaves for the
project, so the label was elided and the check failed on macOS. The frame
is now 260 columns, as the package's other project-label tests draw it.
The label itself is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shortStateRoot and shortEngineHome exist so a workspace host's socket fits
the 104-byte limit. Under the physical /private/var/folders spelling this
PR gives cmd/codeaf's TestMain, host.sock lands a few bytes past it, and
the host, environment, cells and stale-socket tests failed with "too long
a path for a socket". On macOS both roots are now made under /tmp, spelled
physically (/private/tmp) like the package's other fixture roots. Linux
and Windows keep the temporary folder, so they run exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the test/canonical-temp-roots branch from 1dd5494 to 71dda06 Compare November 2, 2026 15:45
@AbirAbbas
AbirAbbas marked this pull request as ready for review November 2, 2026 16:42
@AbirAbbas
AbirAbbas merged commit 91bfa14 into app/v3 Nov 2, 2026
14 of 20 checks passed
@AbirAbbas
AbirAbbas deleted the test/canonical-temp-roots branch November 2, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant