fix(tests): evict the whole git_service package family — 13 leaked failures in test_ent615 (0.9.5 M1) - #2861
Conversation
…parent — 13 leaked failures in test_ent615 (0.9.5 M1) #2487 made `services.git_service` a package. Three test helpers still did `sys.modules.pop("services.git_service")` before re-importing it with stubbed deps. Evicting only the parent leaves `services.git_service.<sub>` cached, and Python's import re-binds a cached submodule onto a NEW parent only on first load — so the re-imported package had no `.token_scrub` / `.conflicts` attribute, and every later `git_service.<sub>` read in test_ent615_token_free_remotes.py failed with `AttributeError: module 'services.git_service' has no attribute 'token_scrub'` (13 failures on dev; the file passes alone). Evict the family (the shape test_reset_preserve_state_guardrails.py already uses) in test_1704_git_service_plugins, test_data_paths_allowlist and test_data_paths_gitignore, and make 1704's autouse restore family-aware for the same reason. Reproduced: `pytest unit/test_data_paths_allowlist.py unit/test_ent615_token_free_remotes.py` → 13 failed; after: 118 passed in both orders. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#2859 names four files; the first commit covered three. test_2075_detect_git_dir.py swept the family but recorded only the package key for undo, so the submodules its own re-import CREATED outlived monkeypatch's teardown — same half-state, same 13 failures. It now carries the lint_sys_modules.py-sanctioned pair (_STUBBED_MODULE_NAMES + an autouse _restore_sys_modules) restoring the whole family; the two data_paths files get the same family-aware restore as 1704 (their parent-only restore fixture was a second leak behind the pop). tests/unit/test_2859_git_service_eviction.py: (1) the mechanism on a throwaway package — parent-only eviction loses the submodule attribute, family eviction rebinds it; (2) an AST guard over tests/unit that fails on `sys.modules.pop("services.git_service")`, `del sys.modules["services.git_service"]` or `monkeypatch.delitem(sys.modules, "services.git_service")` outside the family shape (self-tested on the exact lines the polluters carried; proven red by reintroducing the pop on a copy). Issue's repro table (-p no:randomly, each file then ent615): 102 / 101 / 93 / 101 passed, 0 failed. lint_sys_modules.py: no new violations. Fixes #2859 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/review ReportBranch:
Critical Findings (block merge)None. Informational Findings (review required)[I1] Incomplete-fix completeness (4.14): a fifth parent-only eviction lives outside the guard's reach (Confidence: 8/10) [I2] Test gap (4.9): the guard proves the static rule; the runtime half is proven only by the throwaway-package mechanism test (Confidence: 6/10) [I3] Consistency (4.7): three copies of the same family-restore block (Confidence: 5/10 → appendix) Clean Categories
Summary
Review complete. Next: |
obasilakis
left a comment
There was a problem hiding this comment.
Reviewed at fc8480cce. No blocking findings.
- Repro table on this head (
-p no:randomly, each file then ent615): 1704 → 102 passed, allowlist → 101, gitignore → 93, 2075 → 101, 0 failed. The same 1704 → ent615 pair ondevfails 13, so the probe does detect the leak. - Guard bites: putting
sys.modules.pop("services.git_service", None)back intotest_data_paths_gitignore.pyturnstest_no_test_evicts_only_the_git_service_packagered. - CI head jobs: 16524 passed, 0 failed on all three seeds.
- Teardown order in
test_2075is right: the autouse fixture is set up beforemonkeypatch, so it tears down after monkeypatch's undo.
Non-blocking notes:
_ALLOWLISTexempts all oftest_2075_detect_git_dir.py, so a future parent-only eviction added elsewhere in that file would pass the guard.- The guard only knows
services.git_service, but the trap applies to any package. The one other parent-only pop of a package,services.agent_clientintest_voice_tools.py:237, only runs when the key was absent before the test and a stub was installed, andtest_voice_tools→test_circuit_breakerpasses (103). So not a live leak today.
…first boot serves again (#2862) * fix(onboarding): the getting-started checklist updates without a reload The store fetched the checklist once per page load, so a milestone reached mid-session (creating an agent from the dashboard, or chatting, scheduling or connecting a channel on another page) stayed unticked until a browser refresh. Re-read on every mount, so returning to the dashboard picks up milestones reached elsewhere, and whenever the agent count changes, since agents are created and deleted from the dashboard itself. Refs Abilityai/trinity-enterprise#238 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(deploy): make the rendered Caddyfile readable by the caddy user The 1-Click first boot runs start.sh under `umask 077`. Since the Caddyfile is rendered to Caddyfile.new and moved into place, the new file came out 0600 root, the `caddy` service user could not read it, the restart failed, and provisioning stopped before Trinity started: a droplet created from the v0.9.5-rc4 snapshot never served HTTPS. Overwriting in place had kept the package's 0644, so rc3 booted. chmod 0644 before the move (the file holds no secrets). The new test runs provision_caddyfile under umask 077 and asserts the installed mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: set version to 0.9.5-rc5 Bumps VERSION to 0.9.5-rc5 and points trinity-do-create.sh's default TRINITY_IMAGE_TAG at v0.9.5-rc5 (tied to VERSION by test_2380_installer_release_pin), so the v0.9.5-rc5 tag can be cut off dev with the Caddyfile permission fix for the DigitalOcean Marketplace snapshot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(onboarding): don't re-ask an absent checklist surface on every forced read The checklist now forces a read on every mount, sidebar expand and fleet-size change. On OSS (404) and unentitled (403) builds that turned one swallowed request per session into one per event, each with a console 404, for a card that never renders. A 404/403 now marks the surface absent for the session; a genuine failure (5xx) stays retryable. Addresses review finding I1 on #2862. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert "chore: set version to 0.9.5-rc5" The rc5 bump moves to its own chore PR, as #2838 did for rc4, so this fix can merge on its own and the bump is not coupled to it. Review finding I1 on #2862: the bump must not land ahead of #2861 while dev is red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: set version to 0.9.5-rc5 (restored) Reverts the split in ea0649a. Agreed with the reviewer to keep the rc5 bump here and merge in order instead: #2861 (green dev) first, then this PR, then tag v0.9.5-rc5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
vybe
left a comment
There was a problem hiding this comment.
merge-train: validated individually against dev 3a47f74 (2-PR run, no train branch). Repro confirmed: each of the four polluters → test_ent615 goes 13 failed → 0 failed; random-order git_service slice clean on seeds 12345/67890/99999.
Fixes #2859
0.9.5 work order M1 — the red
dev(backend-unit-testfailing on3a47f74).Root cause
#2487 made
services.git_servicea package. Three test helpers still didsys.modules.pop("services.git_service")before re-importing it with stubbed deps —test_1704_git_service_plugins.py,test_data_paths_allowlist.py,test_data_paths_gitignore.py. Evicting only the parent leavesservices.git_service.<sub>cached, and Python binds a cached submodule onto a new parent only on first load — so the re-imported package had no.token_scrub/.conflictsattribute, and every latergit_service.<sub>read intest_ent615_token_free_remotes.pyfailed withAttributeError: module 'services.git_service' has no attribute 'token_scrub'. 13 failures whenever one of those files runs first (random order underpytest-randomly); the file passes alone.Fix
Evict the whole
services.git_service*family (the shapetest_reset_preserve_state_guardrails.pyalready uses) at the three sites, and make every parent-only_restore_sys_modulesfixture family-aware (1704 and bothdata_pathsfiles — a parent restored without its children is the same half-state). Second commit: the fourth polluter from #2859,test_2075_detect_git_dir.py(swept the family but recorded only the package key for undo, so the submodules its own re-import created outlived teardown) — now the linter-sanctioned_STUBBED_MODULE_NAMES+_restore_sys_modulespair.Regression guard (
tests/unit/test_2859_git_service_eviction.py): the mechanism on a throwaway package (parent-only eviction loses the submodule attribute; family eviction rebinds it) + an AST scan oftests/unitthat fails onsys.modules.pop("services.git_service")/del sys.modules["services.git_service"]/monkeypatch.delitem(sys.modules, "services.git_service")outside the family shape — self-tested on the exact lines the polluters carried, proven red by reintroducing the pop on a copy.Verification
-p no:randomly, each polluter then ent615): 13 failed before on each; after →test_1704102,test_data_paths_allowlist101,test_data_paths_gitignore93,test_2075101 passed, 0 failed.tests/lint_sys_modules.py: no new violations.unit/with the CI flags (-n auto --dist loadfile -p randomly, seed 12345), first commit: 16521 passed, 31 skipped, 0 failed (11 min). The 13 IDs are not added totests/unit_failure_baseline.txt.🤖 Generated with Claude Code