Skip to content

Flaky: run_tests.py intermittently fails host-package drift guard on hosts/claude/.claude-plugin/plugin.json #95

Description

@Kyarha

Symptom

python3 scripts/run_tests.py intermittently fails its final host-package drift guard:

ERROR: committed host packages are stale relative to source.
  1 path(s) differ ... hosts/claude/.claude-plugin/plugin.json

But standalone python3 scripts/build_host_packages.py --check always reports OK: committed host packages are in sync with source, git diff is clean, and build_all is byte-for-byte deterministic across repeated builds. So a test earlier in the suite contaminates process state so that check_drift's in-process scratch rebuild diverges from the committed tree — only when run inside the full suite, not standalone.

Failing test

scripts/test_build_host_packages.py::DriftCheckTests::test_check_passes_when_in_sync
(its assertEqual(code, 0, out.getvalue()) prints the drift text on failure; the
jig-drift-… tempdir prefix in the error confirms it's a DriftCheckTests method).
Inside that test the seed build and the check rebuild both build from REPO_ROOT,
yet _diff_packages reports claude/.claude-plugin/plugin.json differing between them.

Nature of the flakiness

  • Per-process random, not test-order (discovery order is sorted/deterministic).
  • Separate suite processes give different outcomes (repro'd once, passed several times), consistent with PYTHONHASHSEED / cold-cache sensitivity.
  • Fixed-PYTHONHASHSEED sweeps (0–7, 12345, 99991) all passed; warm random runs mostly passed. The first run on a clean checkout (cold __pycache__) failed — a leading, still-untested hypothesis is that cold bytecode caches are the trigger (matches "reproduces on a clean checkout").

Ruled out

  • build_claude_plugin.build copies source .claude-plugin/plugin.json byte-for-byte; output is deterministic given disk state.
  • No test rebinds the build / install_contract / scaffold module attributes (only test_scaffold_mode.py patches scaffold_mod.atomic_write_text, restored in finally).
  • No test writes or deletes the real source .claude-plugin/plugin.json (the .claude-plugin unlink/rmtree calls in tests operate on tempdirs / _seed_valid_repo).
  • Passing runs show 0 write/delete/rename ops to source .claude-plugin (verified with an fs-tracer).
  • CLAUDE_PLUGIN_ROOT is leaked (set to REPO_ROOT, never restored, by skills/memory-sync/test_memory.py:946 and :1035) — but that's deterministic, so it can't by itself explain a flaky failure. Worth ruling in/out as a contributor and fixing anyway (missing tearDown).

The paradox to crack

A pure byte-copy of an unchanged file should make the seed and check builds identical. So the divergence is most likely one of:

  1. a presence difference (one tree missing plugin.json), or
  2. a source mutation performed by a child process (an fs-tracer scoped to the parent process wouldn't see it), or
  3. module/global state that changes the build's file enumeration between the two calls.

Next diagnostic step: capture a failing run's actual scratch-vs-committed bytes — e.g. a temporary dump inside check_drift under if drifted: guarded by an env var — to distinguish content-vs-<absent>, plus a child-process-aware fs-trace.

Repro harness gotcha

Running multiple run_tests.py concurrently deadlocks on git worktree/reservation locks and hangs the suite on a review.py record-review subprocess. Sweep for the flake strictly sequentially. (macOS has no timeout; use a Python Popen + watchdog.)


Not yet root-caused. Filed to keep a trace; help/second opinion welcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions