Skip to content

fix(agent-core): lock ORGII_HOME while the workspace test reads it - #1181

Merged
Harry19081 merged 1 commit into
developfrom
fix/agent-core-workspace-test-race
Sep 1, 2026
Merged

fix(agent-core): lock ORGII_HOME while the workspace test reads it#1181
Harry19081 merged 1 commit into
developfrom
fix/agent-core-workspace-test-race

Conversation

@Chloe-JY

@Chloe-JY Chloe-JY commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Problem

core::definitions::resolved::tests_extended::resolved_workspace_falls_back_to_personal_workspace
fails intermittently:

assertion `left == right` failed
  left: "/Users/…/.orgii/personal/workspace"
 right: "/var/folders/…/T/.tmpFhGY0T/personal/workspace"

The two sides are the same function evaluated at two different moments:

let resolved = ResolvedAgent::resolve(&def, None, &default_overrides())?;  // reads ORGII_HOME
let expected = app_paths::personal_workspace();                            // reads ORGII_HOME again
assert_eq!(resolved.workspace(), expected.as_path());

ORGII_HOME is process-global. test_helpers::test_env already exists to
serialize access to it, and every test that mutates it goes through
test_env::sandbox(). This test mutates nothing — it observes whatever
ORGII_HOME happens to be — so it takes no lock, and a sandboxed test running
in parallel can repoint the variable between the two reads. Whichever read lands
first sees the old value and the assertion fails. The error message flips
direction depending on which side won the race, which is the signature of this
being a timing artifact rather than a logic bug.

test_env's own docs already anticipate this case:

Use lock_home() directly only when you need the serialization but are
managing your own tempdir/env setup (rare — typically just for harnesses that
need to observe the real $HOME).

That is exactly this test: a reader of the ambient value.

Solution

Take test_helpers::test_env::lock_home() at the top of the test, with a
comment explaining why a test that sets no environment still needs the lock.
agent_core already dev-depends on test_helpers, and the guard is
poison-tolerant, so a panicking sandbox test cannot cascade into this one.

Six added lines in one #[cfg(test)] block; no production code is touched, and
no other test's behavior changes.

Resulting invariant: the test's two reads of ORGII_HOME observe the same
value, so its assertion depends only on ResolvedAgent's fallback logic — the
thing it is actually testing.

Potential risks

  • Slightly more serialization. This test now waits behind any sandboxed test
    holding lock_home(). It does no I/O and the suite time is unchanged within
    noise (14.5s → 14.9s for agent_core --lib, inside run-to-run variance).
  • lock_home() is not reentrant. If this test later grows a
    test_env::sandbox() call it would deadlock against itself. It has no reason
    to — it deliberately observes the ambient value — but the added comment says
    so.
  • Other unguarded readers may exist. This fixes the one that actually fails;
    a sweep for every test that reads ORGII_HOME-derived paths without the lock
    is a larger audit and is not attempted here. Scoping it this way keeps the
    change reviewable against a reproduction.
  • This does not make the suite race-free in general — it removes one
    reader-side race. test_env's invariant ("every test that mutates
    ORGII_HOME or HOME MUST go through sandbox()") is still what holds the
    rest together.
  • Rollback: revert this commit. Six lines, test-only.

Verification

  • The race was reproduced deterministically before fixing it. On unmodified
    develop, inserting a 400ms sleep between the two reads — widening the
    existing window, changing no logic — makes it fail every run with the CI
    assertion:

    assertion `left == right` failed
      left: "/var/folders/…/T/.tmpoeLSvM/personal/workspace"
     right: "/Users/…/.orgii/personal/workspace"
    
  • The same widened window passes with this fix applied:
    test result: ok. 3189 passed; 0 failed; 2 ignored. That is what shows the
    lock closes the race rather than the sleep reshuffling the schedule. The
    experimental sleep is not in this diff (grep -c EXPERIMENT → 0).

  • cargo test -p agent_core --lib — 3,189 passed, 0 failed, 2 ignored.

  • cargo clippy -p agent_core --all-targets -- -D warnings — exit 0.

  • Run with a private CARGO_TARGET_DIR: two ORGII worktrees resolve this
    workspace to the same build-script OUT_DIR, so a shared target dir can
    verify the wrong worktree.

  • Not run: no TypeScript changes, so no pnpm typecheck / vitest. No UI
    change, so no screenshots. Full-workspace cargo test was not re-run — this
    diff is #[cfg(test)]-only inside one test function.

  • Pre-commit hook trailer is absent. Committed from a git worktree, where
    .husky/_/husky.sh is gitignored and therefore never created by
    git worktree add. That trailer is structurally always missing in a worktree
    and carries no signal; the check it would have run for a Rust diff is
    cargo clippy, run manually above.

`resolved_workspace_falls_back_to_personal_workspace` reads process-global
`ORGII_HOME` twice — once inside `ResolvedAgent::resolve` and once via
`app_paths::personal_workspace()` — and compares the results. It mutates no
environment, so it took no lock; a sandboxed test repointing `ORGII_HOME`
between the two reads makes them disagree and the assertion fails. The reported
left/right flip direction run to run, which is the tell.

Takes `test_helpers::test_env::lock_home()`, which `test_env`'s own docs
designate for exactly this case: a test that needs the serialization but
manages no env of its own.
@Harry19081
Harry19081 merged commit 4333290 into develop Sep 1, 2026
6 checks passed
@Harry19081 Harry19081 added the tests Test coverage or test infrastructure work label Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test coverage or test infrastructure work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants